Animatronic Skeleton

Instructions for building a complete moving skeleton

This is the Skeleton that I started with. I got it at Spencer Gifts last October for $25. From a stamp on the back shoulder blade, it appears that it was made by a company called "The Paper Magic Group" and has a date of 1998. It was made in China and is about three feet tall. I removed the top of the skull by gently prying it apart along the glue seam with an exacto knife. The wire you see sticking up out of the skull is for the LED's I added to his eyes.To see a larger version, click on the picture.The tools I used included a battery powered hand drill and various bits. A Dremel Tool and cutoff bits, wire cutters, pliers, and screwdrivers, exacto knife, and a soldering iron. All parts except the skeleton, gears, and electronics can be purchased at the hardware store and hobby shop.
The Skeleton is made of a very brittle plastic and breaks easily, but can also be easily repaired with a little putty epoxy (there are several brands, any will do)


The Shoulder
The side view shows the gears used to move the shoulder. The small gear is attached directly to the arm. It is driven by the larger gear which is attached to the servo. The servo is connected to the skeleton with epoxy.
     








To animate the arm, I broke it off at the movable joint (that takes guts!), then I drilled out the joint and filled it with putty epoxy (there are several brands available from most hardware stores, any will do. ). Before the epoxy hardened, I inserted a 3 to 4 inch stove bolt and positioned it correctly, then packed the putty around it. Once the epoxy had hardened (full cure in about half an hour), I threaded a small gear (T30) that I got from All Electronics (CAT# GR-5 $2.75 per set of 5) and snugged it into place with a little more epoxy and a nut.

On the body side, I filled it with epoxy and inserted another stove bolt to align the hole, but before it hardened, I removed the bolt. Once it was hard, I drilled it out a bit more and inserted a piece of brass tubing just large enough to hold the bolt. You can see the end in the picture below near the large gear.
     
Next I took a CIRRUS CS-60BB servo I purchased from Sheldon's Hobby for $24.99 and replacing the short center screw with a longer one from the servo parts pack, I attached a T40 gear (from the same 5 pack I got above) to the servo horn (the white piece). I drilled a small hole for an additional screw to tie the servo horn and the gear together.

When you choose a servo, there are two numbers that you want to look at. The Torque and the Speed. The shoulder requires a 60 to 100 oz/in servo, but a slower speed servo in this case is better. The servo will only move about 180 degrees. With the gears that I chose, the small gear will turn about 3/4 of a turn, giving the arm a range of over the head to behind the back which is about what a human arm will do. Since even the slowest servos will move the full 180 degrees in less than 1 1/2 seconds, a slow servo will still sling the arm around pretty fast, fast enough for a startle.

The first time I tried this, I used a faster servo and a smaller gear (a T20). The servo had plastic internal gears. The arm moved much faster, but after about an hour of use, the internal gears started breaking off.

I switched to a slower servo with metal internal gears and a T30 gear on the arm. This servo has held up much better even though the oz/in rating was about the same. As of this writing, it has over 20 hours of operation with no problems.
     

To attach the servo to the shoulder, with the gears in place, I mixed up another batch of epoxy putty and shaped it onto the shoulder blade and pressed the servo into it until the gears met, keeping them straight and tight. I had to hold them in place for a few minutes while the epoxy hardened. Then I added more epoxy to fill in the gaps around the edges so that I had a solid base for the servo.

Since the first servo failed, I pried it loose from the epoxy with a screwdriver and attached the new servo to the same place with some gap-filling super glue.

The Skull

Next I went to work on the skull. The skeleton I chose comes with a skull that has a movable jaw. I got a piece of piano wire from the hardware store and bent it into fit along the jaw and stick up through the jaw hinge holes (I had to make the hole a bit larger with an exacto knife). You can see the wire along the back edge of the jaw in the pictures below. I drilled a small hole in the jaw and bent the piano wire to fit through it. I also bent the top of the piano wire to fit into the linkage to the servo in the skull. (see below)

When the servos turn the head and the jaw opens and closes, the jaw hits the collar bone. To reduce this problem, I added metal washers on the neck to lift the skull up a bit higher so that when the jaw opens, it will clear the collar bone. Three washers are not quite enough, but any more and the neck does not protrude into the skull enough for control.



I then covered the piano wire with putty epoxy to prevent the wire from moving in relationship to the jaw when the servo pushed or pulled the wire. This makes the jaw movement much more exact.
     





This is the inside of the skull. If you look hard, you can see the piano wire sticking up out of the jaw hinge near the bottom of the picture. You can also see the wiring of the LEDs for the eyes.

The putty was just some left over from the shoulder work. I knew that I would need to fill in some space for the jaw servo mount.



To connect the servo to the neck, I used a 2-liter pop bottle cap and a control horn. I hacked a hole into the top of the cap, then I glued the control horn inside the cap with some putty.
     





I glued the two servos that fit into the skull together with some thick super glue. The round control horn gets removed and replaced with the bottle cap I prepared above. I then cut one side of the lever control horn off. This servo controls the jaw.

Here, I am using FMA S355M servos that I got for $21.95 by mail order from FMA Direct. It took about 10 days to get them. I do not need the high 114 oz/in torque here, but the servos were handy. These would have been a good choice for the arm, but I had not received them yet.
 
Here are the servos mounted inside the skull. The jaw servo is connected to the jaw lever (piano wire) with a piece of square brass tube with a hole drilled in either end. When I took this picture, I had a spacer between the control horn and the brass link, but I later removed it to make things align better.
 



This is another view of the skull servos. Here you can see that the bottle cap is pined to the neck by drilling a hole through it and the neck and inserting a pin. The bottle cap is bigger than the neck which allows it to float. That way, even if the servo and the neck are not aligned exactly, they still work well together as the neck is free to move small amounts along the pin as the skull turns.
 


It is a bit hard to see since it is off white, by if this picture shows the putty that I used to form a mounting platform for the servos. I just mixed up a batch of putty, put it in place, then pushed the servo into it until it was in the right position. A few minutes later it was solid and hard.




The Electronics


Description

 Part Number

 Source

 Approx Cost
 1 - SYNTAX ProtoBoard (cut in fourths)
 PC-462905

 HSC

$3.95
 1 - Servo Control Chip
FT639

 FerretTronics

$22.95
 1 - 8 pin DIP socket

 Radio Shack

 1 - 10K resistor
271-1335

 Radio Shack

 $0.49
 1 - 22K resistor
 271-1339

 Radio Shack

 $0.49
 1 - Switching diode
 276-1122

 Radio Shack

 $1.19
 2 - 0.1 µf capacitors (not shown)

  Radio Shack

 1 - 40-pin Snap Apart Headers
 307SS40G

 HSC

 $0.79
 3M Jumper Wire Kit (optional, but nice) 517-923351R
 Mouser

 $20.18
 1 - Surplus PC power supply (+5V@10A)
 ???

 Junk PC

 $0 to $20


The SYNTAX PCB board is a good quality board and is relatively inexpensive. I generally keep several around. In this case, I want to put the board inside the Skull, so I cut the board (tin snips) and will only use one corner. The two smaller pictures are the front and back of the corner I chose. After cutting the PC board, take the small piece of sandpaper that comes with the epoxy putty and sand the edges and the board will be much nicer to work with.

If you look at the board, it is arranged the same as a solder-less bread board. This allows you to lay out things on the breadboard, then transfer them directly to the PCB. In this case, the circuit is so simple, that we can just solder it up.
   

The circuit we are building is really quite simple. Click on the picture to see a larger version.

In the picture,

    Red is the power (+5V),
    Green is ground,
    Brown is the RS232 signal from the controlling computer,
    Blue is the servo signal.

Although they are often not shown in schematics, as always in digital circuits, we add some 0.1 µf capacitors from +5V to ground. These prevent noise from the motors inside the servos from affecting the logic.

Caution: I think that AirTronic Servos (and maybe others) reverse the ground and +5V lines on their connectors. Make sure that you check polarity.
     
This is the board with the parts in place, ready to be soldered. The blue and orange lines are the servo signal lines. The 3 by 5 array of pins is where you plug the servos in. The center column of 5 pins all carry +5 volts. The left 5 are all ground, and the right 5 are the 5 servo signal lines. Each servo uses three pins, ground, +5, and one of the signal pins.

The small connector in the upper left hand corner is for power (+5V) and the other connector in the left center is for the serial port (2400 bps from the PC or Macintosh).

RS232 is a -12V to +12V signal. We want logic levels (0 to +5V) so we add a diode to clip the voltage, throwing away the negative part, and we use the two resistors as a voltage divider to drop the (up to) +12V down to a max of +5V.

Here is the board at a different angle. You can see the two 0.1µf (104) capacitors better here. These caps absorb any noise from the power lines.
 





The back side of the board. If you have never soldered before, take a look at Harry Lythall's page on basic soldering techniques.
     






Now we are ready to mount the board. The side of the servo looked like a likely place, so I mixed up a tiny bit of putty epoxy and rolled it into four pea sized balls and stuck them to the side of the servo. Then I pressed the finished PC board into them.

Finally, I plugged the three servos and the LED eyes into the board. The LED's only use the power and ground pins. If I decide to add two more servos, I will add two more pins for the eyes, but for now, it is handy to just use an unused servo connector to power them.

Here you can see the routing of the servo wires to the PC board and the putty holding the PC board to the side of the neck servo.







The Software
The initial RealBasic code is intended to serve as an example of how you control the animatronic. It allows you to control any of the three servos by sliding one of the three sliders in the Manual Box, or you can press one of the buttons to generate random movements.

Main Routines

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
Main.Open:
Sub Open()
  dim r as boolean
  dim i,rc as integer
  
  bugLog = new debugWindow
  bugLog.write "Surveyor Startup"
  
  appPrefs= New prefFileItem
  appPrefs.fileInfo= new folderItem
  rc=appPrefs.getText("Surveyor.prf")
  if rc=1 then  'create default prefs
    appPrefs.setCreatorType "smpl","pref"
    appPrefs.writeitem "Surveyor","SerialPortName",str(1)
    appPrefs.writeitem "Surveyor","MaxDelay",str(100)
    appPrefs.putText
  end if
  
  SerialPortName.listindex = val(appPrefs.readItem("Surveyor","SerialPortName"))
  MaxDelay.text = appPrefs.readItem("Surveyor","MaxDelay")
  
  'me.top     = screen(0).top  + 23
  'me.left    = screen(0).left + 10
  'me.width   = 685
  'me.height  = 200
  me.visible = true
  
  
  me.show
  me.refresh
  
  ft.servoinit(1)
  ft.servoinit(2)
  ft.servoMove(0,neck,75)
  ft.servoMove(0,jaw,0)
  ft.servoMove(0,arm,150)
  MoviePlayer1.play
End Sub

Main.SerialPortName.Open:
Sub Open()
  dim count,i as integer
  dim x as string
  
  count = System.SerialPortCount
  
  if count <> 0 then
    for i = 0 to count-1
      me.addrow System.SerialPort(i).Name
    next
  end
  
End Sub

Main.SerialPortName.Change:
Sub Change()
  dim r as boolean
  dim i,rc as integer
  
  appPrefs.writeitem "Surveyor","SerialPortName",str(SerialPortName.listindex)
  appPrefs.putText
  
  if sp <> nil then
    sp.close
    sp = nil
  end if
  
  thePort.SerialPort = System.SerialPort(SerialPortName.listindex)
  
  r = thePort.open
  sp = thePort
  
  if r then 
    bugLog.write System.SerialPort(SerialPortName.listindex).name + " opened ------------*"
    
    ft = new FerretTronics(sp)
    
    me.refresh
    
    
  else
    msgbox "Could not open Serial Port"
    //quit
  end if
  
End Sub

Main.JawSlider.ValueChanged:
Sub ValueChanged()
  ft.servoMove(0,jaw,me.Value)
End Sub

Main.HeadTurn.ValueChanged:
Sub ValueChanged()
  ft.servoMove(0,neck,me.Value)
End Sub

Main.RunSkel1Head.Action:
Sub Action()
  ft.servoinit(1)
  ft.servoMove(1,neck,75)
  ft.servoMove(1,jaw,0)
  if SkeletonThread = nil then
    bugLog.write me.name + " started"
    SkeletonThread = new SkeletonThreadClass
    SkeletonThread.run
    me.caption = "Stop"
  else
    SkeletonThread.notDone = false
    SkeletonThread = nil
    bugLog.write me.name + " stopped"
    me.caption = "Run"
  end if
End Sub

Main.MaxDelay.KeyDown:
Function KeyDown(Key As String) As Boolean
  if val(me.text) > 6000 then 
    beep 
    me.text = "6000"
  end
  'appPrefs.writeitem "Surveyor","MaxDelay",me.text
  'appPrefs.putText
  
End Function

Main.ArmSlider.ValueChanged:
Sub ValueChanged()
  ft.servoMove(1,arm,me.Value)
End Sub

Main.RunSkel1Arm.Action:
Sub Action()
  ft.servoinit(1)
  ft.servoMove(1,arm,150)
  if Skel1RightArmThread = nil then
    bugLog.write me.name + " started"
    Skel1RightArmThread = new Skel1RightArmClass
    Skel1RightArmThread.run
    me.caption = "Stop"
  else
    Skel1RightArmThread.notDone = false
    Skel1RightArmThread = nil
    bugLog.write me.name + " stopped"
    me.caption = "Run"
  end if
End Sub

Main.MoviePlayer1.Open:
Sub Open()
  dim f as folderItem
  f=New FolderItem
  f=GetFolderItem("Cauldron.mov")
  MoviePlayer1.movie=f.OpenAsMovie
  MoviePlayer1.play
  
End Sub

FerretTronics Subroutines 

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
FerretTronics.FerretTronics:
Sub FerretTronics(serialPort as serial)
  '---------------------------------------------------------------
  'Constructor - Inits the FerretTronics Class
  '---------------------------------------------------------------
  
  dim r as boolean
  dim i as integer
  
  cCHIPSELECT = 239
  
  cACTIVE = 117
  cLONGPULSE = 90
  cSHORTPULSE = 85
  cHEADER = 96
  cSETUP = 122
  
  '----------------------------------------------------------------------
  
  sp = serialPort
  
  bugLog.write "FerretTronics Object Initialized - Copyright 1999, FerretTronics Inc."
  
End Sub

FerretTronics.servoMove:
Sub servoMove(chip as integer,servo as integer, position as integer)
  '------------------------------------------------------------------
  'sends the bytes required to move a servo (FT639) to a new position
  '------------------------------------------------------------------
  DIM uV  as integer
  DIM lV  as integer
  
  me.routeChipSelect(chip)
  
  uV = position / 16
  lV = position - (uV * 16) 
  uV = uV + 128 + (servo - 1) * 16 
  lV = lV + (servo - 1) * 16 
  
  sp.write chr(lV) + chr(uV)
  'waitTicks(10)
  bugLog.write "Moving Servo " + str(servo) + " to " + str(position)
  
End Sub

FerretTronics.routeChipSelect:
Sub routeChipSelect(chip as integer)
  '---------------------------------------------------------------
  'Sends a chip select signal to the FT649 to select one of five
  'attached FT609 or FT639 chips
  '---------------------------------------------------------------
  dim xchip as integer
  
  xchip = chip 'copy so we can modify it
  
  if (xchip < 0) or (xchip > 5) then 
    msgbox "The selected chip (" + str(chip) + ") is out of range (0 to 5)"
    xchip = 0
    beep
  end if
  
  if xchip <> 0 then
    sp.write chr(cCHIPSELECT+xchip)
    buglog.write "Chip " + str(xchip) + " selected"
  end if
  
  
End Sub

FerretTronics.servoLongPulse:
Sub servoLongPulse()
  '---------------------------------------------------------------
  'Sets FT639 Longpules mode
  '---------------------------------------------------------------
  sp.write chr(cSETUP) + chr(cLONGPULSE) + chr(cACTIVE)
End Sub

FerretTronics.servoShortPluse:
Sub servoShortPluse()
  '---------------------------------------------------------------
  'Sets FT639 ShortPulse mode
  '---------------------------------------------------------------
  sp.write chr(cSETUP) + chr(cSHORTPULSE) + chr(cACTIVE)
End Sub

FerretTronics.servoSetHeader:
Sub servoSetHeader(head as integer)
  '---------------------------------------------------------------
  'Sets FT639 Header value
  '---------------------------------------------------------------
  sp.write chr(cSETUP) + chr(cHEADER+head) + chr(cACTIVE)
End Sub

FerretTronics.servoInit:
Sub servoInit(chip as integer)
  '---------------------------------------------------------------
  'Sets FT639 chip to a known state
  '---------------------------------------------------------------
  me.routeChipSelect(chip)
  me.servoLongPulse
  me.servoSetHeader(4)
  bugLog.write "Initialization commands sent to Servo " + str(chip)
End Sub

FerretTronics.waitTicks:
Sub waitTicks(delayticks as double)
  dim targetTick as double
  targetTick = Ticks + delayTicks
  
  while targetTick > Ticks
    
  wend
  
End Sub

FerretTronics.DataAvailable:
Sub DataAvailable()
  '---------------------------------------------------------------
  'Reads incoming data from the FT629 and stores the switch states
  'array. To access, use ft.switch(n)
  '---------------------------------------------------------------
  dim n,i as integer
  dim buffer as string
  beep
  buffer = sp.readAll
  n = len(buffer)
  while n > 0
    for i = 0 to 4   'Check each of the five switch values
      if bitwiseAnd(asc(mid(buffer,n,1)),pow(2,i) ) <> 0 then
        switch(i) = true   'Switch is on
      else
        switch(i) = false  'Switch is off
      end if
    next
    n = n - 1
  wend
End Sub

Skeleton Thread 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 SkeletonThreadClass.Run:

Sub Run()
  dim i,r,r2 as integer
  dim arm,narm as integer
  
  me.notDone = true
  
  while me.notDone
    ft.servoinit(1)
    
    r = (rnd*val(main.MaxDelay.text))+1
    bugLog.write "delay " + str(r)
    ft.waitTicks r
    
    if me.notDone and (rnd>.50) Then
      bugLog.write "Left Head to center"
      
      r = (rnd*30)+1
      for i = 25 to 90 step r
        ft.servoMove(0,neck,i)
        'ft.waitTicks 1
      next
    end if
    
    if me.notDone and (rnd>.60) Then
      bugLog.write "Talk"
      r = (rnd*5)+1
      for i = 1 to r
        r2 = (rnd*20)+20
        ft.waitTicks (r2/3)
        ft.servoMove(0,jaw,r2)
        ft.waitTicks (r2/3)
        ft.servoMove(0,jaw,0)
        
        'ft.waitTicks 30
        'ft.servoMove(0,jaw,r2)
        'ft.waitTicks (r2/3)
        'ft.servoMove(0,jaw,0)
      next 
    end if
    
    if me.notDone and (rnd>.50) Then
      bugLog.write "Center Head to right"
      r = (rnd*30)+1
      for i = 90 to 170 step r
        ft.servoMove(0,neck,i)
        'ft.waitTicks 1
      next
    end if
    
    
    if me.notDone and (rnd>.50) Then
      bugLog.write "Right Head to Left"
      r = (rnd*30)+1
      for i = 170 downto 25 step r
        ft.servoMove(0,neck,i)
        'ft.waitTicks 1
      next
    end if
    
    
  wend
  ft.servoMove(0,neck,75)
  ft.servoMove(0,jaw,1)
  
End Sub

Skel1RightArmClass 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Skel1RightArmClass.Run:
Sub Run()
  dim i,r,r2 as integer
  dim armP,narm,armstep as integer
  dim b as string
  
  me.notDone = true
  b = " "
  for i = 1 to 8
    b = b+b
  next
  
  ft.servoinit(1)
  armP = 75
  ft.servoMove(0,arm,armP)
  
  while me.notDone
    r = (rnd*val(main.MaxDelay.text))+1
    ft.waitTicks r
    
    if me.notDone and (rnd>.10) Then
      'bugLog.write "Arm"
      r  = (rnd*20)+40
      r2 = (rnd*4)+1.1
      r2 = r2 - 2.5
      
      if rnd>.9 then
        narm = 90
      else
        narm = armP + (r2*r)
      end if
      
      if narm > 200 then 
        narm = 200  
      end if
      
      if narm < 0 then 
        narm = 0
      end if
      
      armstep = rnd*5 +1
      
      
      While armP > narm 
        armP = armP-armstep
        ft.servoMove(0,arm,armP)
        bugLog.write "Arm " +str(armP) + " " + str(r2) + Right(b +"*",armP/5)
      wend
      
      While armP < narm 
        armP = armP+armstep
        ft.servoMove(0,arm,armP)
        bugLog.write "Arm " +str(armP) + " " + str(r2) + Right(b +"*",armP/5)
      wend
    end if
    
  wend
  ft.servoMove(0,arm,75)
  
End Sub

Preferences Subroutines 

Many thanks to D. Zouras for this module. Unfortunately, I have lost the link to his page. If anyone knows where it is, let me know.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
prefFileItem.writeItem:
Sub writeItem(sectionName as string, itemName as string, itemValue as string)
  'this sub will either update the value of itemName with itemValue if it exists
  'or it will create itemName and assign itemValue to it if it doesn't exist
  'if the sectionName does not exist, that will be created also
  
  dim temp, itemText as string
  dim sectionHeading,itemLabel as String
  dim sectionPos,nextSectionPos,itemPos,itemEndPos as integer
  dim headingLen,itemsLen,labelLen as integer
  dim beforeItem, afterItem as string
  
  
  temp=me.fileText
  sectionHeading="["+sectionName+"]"
  headingLen=len(sectionHeading)+1  'add 1 for return at end of line
  itemLabel=itemName+"="
  labelLen=len(itemLabel)
  
  sectionPos=instr(temp,sectionHeading)
  if sectionPos>0 then
    nextSectionPos=instr(sectionPos+headingLen,temp,"[")
    if nextSectionPos<>0 then
      itemsLen=nextSectionPos-(sectionPos+headingLen)
      itemText=mid(temp,sectionPos+headingLen,itemsLen)
      itemPos=instr(itemText,itemLabel)
      itemEndPos=instr(itemPos,itemText,CHR(13))
    else  'no next section
      itemText=mid(temp,sectionPos+headingLen)
      itemPos=instr(itemText,itemLabel)
      'itemEndPos=len(itemText)+1
      itemEndPos=instr(itemPos,itemText,CHR(13))
    end if
    
    if itemPos>0 then 'exists
      'breaking item string apart to insert itemValue
      beforeItem=mid(itemText,1,itemPos+labelLen-1)
      afterItem=mid(itemText,itemEndPos)
      itemText=beforeItem+itemValue+afterItem
    else  'item does not exist
      itemText=itemText+itemName+"="+itemValue+CHR(13)
    end if
    'breaking entire prefs string apart to reinsert the updated itemText
    beforeItem=mid(temp,1,sectionPos+headingLen-1)
    if nextSectionPos=0 then
      afterItem=""
    else
      afterItem=mid(temp,nextSectionPos)
    end if
    temp=beforeItem+itemText+afterItem
    me.fileText=temp
  else  'section not found
    me.fileText=me.fileText+CHR(13)+sectionHeading+CHR(13)+itemName+"="+itemValue+CHR(13)  'add section & item
  end if
  me.saved=false
End Sub

prefFileItem.readItem:
Function readItem(sectionName as string, itemName as string) As String
  'returns value for itemName if found, else returns null
  
  dim temp, itemText as string
  dim itemValue As String
  dim sectionHeading,itemLabel as String
  dim sectionPos,nextSectionPos,itemPos,itemEndPos as integer
  dim headingLen,itemsLen,labelLen as integer
  
  temp=me.fileText
  sectionHeading="["+sectionName+"]"
  headingLen=len(sectionHeading)+1  'add 1 for return at end of line
  itemLabel=itemName+"="
  labelLen=len(itemLabel)
  
  sectionPos=instr(temp,sectionHeading)
  if sectionPos>0 then
    nextSectionPos=instr(sectionPos+headingLen,temp,"[")
    if nextSectionPos<>0 then
      itemsLen=nextSectionPos-(sectionPos+headingLen)
      itemText=mid(temp,sectionPos+headingLen,itemsLen)
      itemPos=instr(itemText,itemLabel)
      itemEndPos=instr(itemPos,itemText,CHR(13))
    else  'no next section
      itemText=mid(temp,sectionPos+headingLen)
      itemPos=instr(itemText,itemLabel)
      'itemEndPos=len(itemText)+1
      itemEndPos=instr(itemPos,itemText,CHR(13))
    end if
    
    if itemPos>0 then 'exists
      ItemValue=mid(itemText,itemPos+labelLen,itemEndPos-(itemPos+labelLen))
    else  'no item to read
      itemValue=""
    end if
  else  'section not found
    itemValue=""
  end if
  
  return itemValue
  
End Function

prefFileItem.deleteItem:
Sub deleteItem(sectionName as string, itemName as string)
  'deletes itemName and its itemValue if it exists
  
  dim temp, itemText as string
  dim sectionHeading,itemLabel as String
  dim sectionPos,nextSectionPos,itemPos,itemEndPos as integer
  dim headingLen,itemsLen,labelLen as integer
  dim beforeItem, afterItem as string
  
  
  temp=me.fileText
  sectionHeading="["+sectionName+"]"
  headingLen=len(sectionHeading)+1  'add 1 for return at end of line
  itemLabel=itemName+"="
  labelLen=len(itemLabel)
  
  sectionPos=instr(temp,sectionHeading)
  if sectionPos>0 then
    nextSectionPos=instr(sectionPos+headingLen,temp,"[")
    if nextSectionPos<>0 then
      itemsLen=nextSectionPos-(sectionPos+headingLen)
      itemText=mid(temp,sectionPos+headingLen,itemsLen)
      itemPos=instr(itemText,itemLabel)
      itemEndPos=instr(itemPos,itemText,CHR(13))
    else  'no next section
      itemText=mid(temp,sectionPos+headingLen)
      itemPos=instr(itemText,itemLabel)
      'itemEndPos=len(itemText)+1
      itemEndPos=instr(itemPos,itemText,CHR(13))
    end if
    
    if itemPos>0 then 'exists
      'breaking item string apart to delete item
      beforeItem=mid(itemText,1,itemPos-1)
      afterItem=mid(itemText,itemEndPos+1)
      itemText=beforeItem+afterItem
    else  'item does not exist
      'do nothing
    end if
    'breaking entire prefs string apart to reinsert the updated itemText
    beforeItem=mid(temp,1,sectionPos+headingLen-1)
    if nextSectionPos=0 then
      afterItem=""
    else
      afterItem=mid(temp,nextSectionPos)
    end if
    temp=beforeItem+itemText+afterItem
    me.fileText=temp
  else  'section not found
    'do nothing, can't find item in nonexistent section
  end if
  me.saved=false
End Sub

prefFileItem.deleteSection:
Sub deleteSection(sectionName as string)
  'deletes the entire section and related items under sectionName
  
  dim temp, itemText as string
  dim sectionHeading,itemLabel as String
  dim sectionPos,nextSectionPos as integer
  dim headingLen,itemsLen as integer
  dim beforeItem, afterItem as string
  
  temp=me.fileText
  sectionHeading="["+sectionName+"]"
  headingLen=len(sectionHeading)+1  'add 1 for return at end of line
  
  sectionPos=instr(temp,sectionHeading)
  if sectionPos>0 then
    nextSectionPos=instr(sectionPos+headingLen,temp,"[")
    if nextSectionPos<>0 then
      itemsLen=nextSectionPos-(sectionPos+headingLen)
      itemText=mid(temp,sectionPos+headingLen,itemsLen)
    else  'no next section
      itemText=mid(temp,sectionPos+headingLen)
    end if
    
    'breaking entire prefs string apart to delete the itemText
    if nextSectionPos=0 then
      beforeItem=mid(temp,1,sectionPos-2)
      afterItem=""
      msgBox itemText
    else
      beforeItem=mid(temp,1,sectionPos-1)
      afterItem=mid(temp,nextSectionPos)
    end if
    temp=beforeItem+afterItem
    me.fileText=temp
  else  'section not found
    'nothing to delete
  end if
  me.saved=false
End Sub

prefFileItem.clear:
Sub clear()
  'reset the fileText property
  'still need to putText if you want to clear the file on disk
  
  me.fileText="This file created with Pref Maker class.  ©D. Zouras 1998"+CHR(13)
  me.saved=false
End Sub

prefFileItem.getText:
Function getText(prefFile as string) As integer
  'reads the folderItem with name prefFile in the systemFolder:PreferencesFolder
  '0=success  1=file did not exist, created blank
  
  dim inStream As TextInputStream
  dim outStream as textoutputstream
  dim prefFolder as folderitem
  dim rc as integer
  
  prefFolder=preferencesFolder
  //assign the path
   me.fileInfo= getfolderItem(prefFolder.absolutepath+prefFile)
  //see if we need to create a new prefs file or not
  if me.fileInfo.exists then
    inStream=me.fileInfo.OpenAsTextFile
    me.fileText=inStream.readAll
    me.fileText=mid(me.fileText,1,len(me.fileText)-1)
    inStream.close
    rc=0
  else  'create new pref file
    me.fileText="This file created with Pref Maker class.  ©D. Zouras 1998"+CHR(13)
    outStream=me.fileInfo.CreateTextFile
    outStream.WriteLine me.fileText
    outStream.Close
    rc=1
  end
  me.saved=true
  
  return rc
End Function

prefFileItem.putText:
Sub putText()
  'writes the text in the fileText property to the folderItem of this object
  
  dim outStream As TextOutputStream
  dim type,creator as string
  
  creator=me.fileInfo.MacCreator
  type=me.fileInfo.MacType
  
  outStream=me.fileInfo.CreateTextFile
  outStream.WriteLine me.fileText
  outStream.Close
  setCreatorType creator,type
  me.saved=true  'just wrote to disk
  
End Sub

prefFileItem.setCreatorType:
Sub setCreatorType(creator as string,type as string)
  'set the creator and type for the prefFile object
  'if a parameter is not 4 chars long then no change will be made
  'to it, but the other is independently tested
  
  if len(creator)=4 then
    me.fileInfo.MacCreator=creator
  end if
  if len(type)=4 then
    me.fileInfo.MacType=type
  end if
End Sub

Original author: Chuck Rice
Original URL: http://www.wildrice.com/Halloween/Construction/Skeleton/