Lesson 4

A heavy duty (100 ton plus, perhaps) roadheader from the Ukraine.This lesson introduces us to some heavy duty concepts. We've been taking it relatively easy so far, so strap yourself  in and get ready to twist your melon.

We'll cover:

  • hash tables
  • indirection
  • modules
  • error catching

These will all become clear as we go along, so lets go!


Take a look over the code. It has grown up a bit, moved out of home and had a baby!

We're about to get into what all of this means, but for the moment you can just have a look through the code and see what looks new.

Open these both in a tab, and flick back and forth between this explanantion, and the code.

The bulk of our code from lesson 4 remains unchanged in the middle of this, but it is wrapped in all sorts of other exciting stuff. Lets start from the very begining, I hear that it is a very good place to start.

Modules – a quick peck on the cheek

module BVNoffice
    …
end

Modules do a lot of things in ruby, a lot of very complicated things, but also a few simple things. The simple way to think about them for our needs is like a picket fence around our code. Imagine that the plot that we've built our bit of code on is a house in an American suburb, and that all the houses are fenced in neatly painted picket module fences. This means that we can be safe inside our module. We don't need to worry about which bathroom we are talking about when we say 'the bathroom'. We know that if there is only one in our module (we are only allowed one), and that we are inside our module that we can't get confused. The BVNoffice module is just a fence around our code. We will use modules in an ever so slightly more powerful way in a bit, so keep this bubbling in your head.


The next line is also new, on the surface it's pretty clear what it does! begin shouldn’t keep you up at night just yet, it gets us moving along. Again, we are going to use this in a slightly more complicated way in a little while.

Modules – a little séance

This next chunk takes us back to modules. We are refering to three external files here. The first is like a toolbox, it's a place to keep our refactored code. The other two are just for tidying up, they are large datasets, so they are out in their own little module just to keep them tidy.

The helper functions are here, People is here and Seats is here, download them and put them in your SketchUp plugins folder to keep things simple.

Toolbox module

In the last lesson (and for your homework!) we refactored the big chunk of code so that it was easier to read, logical sections were moved to the outside of the main chunk so that they could be refered to just by a useful name. This use for modules is similar, except it puts those bits of code into a tidy external file so that they are available whenever you want them.

module BVN_Functions
  
  def self.test()
    puts 'this seems to be working'
  end

end

This is a cut down version of the helper module; lets step through it. module BVN_Functions tells ruby that it is a module and that BVN_Functions is its name. Bear in mind that whenever you are going to talk about anything in this module that you'll need to type out the name of the module before the name of the thing in the module, so whilst My_awesome_module_that_I_wrote_last_week_when_I_was_very_very_drunk is descriptive, and I’d like to encourage descriptive naming wherever possible, it's a bit of a mouthful, something a bit shorter would be good. The other thing is that module names must start with a capital letter, so My_functions is great, my_functions would fail.

We've seen def used to define functions before, but we've never seen self used. self is a special word that has a whole load of powers, but as, sadly, we've started to get accustomed to, we're just going to scratch the surface of what it does. To put things very crudely self registers the function to the module, and makes it available from outside by using the dot operator on the name of the module. In this case, if we load module and that BVN_Functions into another file then we’ll be able to call BVN_Functions.test and it will print this seems to be working to the console.

We'll see this used fo shizz in the real code, so hang on and we'll get there soon.

Tidying modules

One of these is a list of the seats in the Sydney BVN Studio, and the other is a list of the people.

Require loads an external file full of code into your project. It is a bit like an Xref in Autocad, or a reference in Microstation.

  1. #import the functions
  2. require '2011_02_26FunctionsForLesson4.rb'
  3. #import the data files
  4. require '2010_11_11peopleData' #information about people
  5. require '2010_11_11seatPositions.rb' #information about the seats
  6. seats = Seats::Positions #/__localise the information so
  7. people = BVNpeople::Data #\ that it is in a nice local variable

Localisation

Constants

Constants are like variables, except that they don't vary. It's pretty simple really!

You'd use one to declare something that you don't want to be able to change, something like the value of Pi, that's not ever going to change, so you can declare it as a constant.

Ruby knows that something is a constant because it's name starts with a capital letter.

In the top of the BVNpeople file it defines a the module, then it declares a constant:

module BVNpeople

     Data = …..

There is a hash table after that which has a heap of data in it, but we'll come back to that in a second, for the moment lets focus on importing that data into our programme. The require statement makes the module available inside our code, we could stop there and refer to the constant in it as Seats::Positions each time we wanted to refer it it, but I’m lazy so I’ve localised it into a variable that is defined withing the scope of this program called seats.

So,  if we skip on a few more lines we start to use this in ernest.

Hash – no, really, you'll want some

  1. xCoord = seats[person[1][:new_Seat_101015]][😡].mm
  2. yCoord = seats[person[1][:new_Seat_101015]][:y].mm

a hyperventilating womanOK, deep breaths! If that line didn't make you hyper-ventilate a bit then you are either wastign your time reading this, or you weren't paying attention! there is an enormous amount going on here crammed into each of these little lines. We are going to need to wind back a long way to make all of this make sense.

The first bit is simple, it is a variable declaration, so your pulse should be slowing a little. We've seen the very last bit before too, converting the number from inches to milimeters.

That just leaves us with this: seats[person[1][:new_Seat_101015]][😡] which we’ll unpick carefully now.

seats is a hash table. Hash tables are a lot like arrays, but it has the advantage that it can be accessed directly by using a key rather than an index. This means that the order of things in it is pretty much irrelavent, just as long as the key-value pairs are right.

{😡 => 2500,:y => 14500}

:symbols

Remember when we started we talked a lot about strings? Well strings are heavy weight things, they take a lot of power to heft all those words around. If you never need to use the contents of the string, and you are just using it to describe things then you can use a symbol. Symbols are like super-lightweight strings. You can make a symbol by just sticking a colon to the begining of a word like :marmite or :vegemite

Back to the hash

Hashes can be keyed with strings of symbols. We're going to use both, but the hash above keys with symbols. The pattern is: key => value. The symbol in the middle is called a hash rocket!

B1‘ => {😡 => 2500,:y => 14500}

This is actually a nested hash the key ‘B1‘ has a value that is actually another hash.

Indirection

We are about to learn about a very neat and elegant trick called indirection. It means to hold a value in an array which isn't actually the final value, but just another clue on the trail of where to look. This comes from programming really low level languages where things were all about values, or pointers to values, but we are so far away from that we can just sigh and nod about how quaint it was. I first learnt about indirection from Paul Coates, who helped me out when the only way I could figure out to wore something was to write about 1000 lines. He said, in his wise way "Use indirection dude" and an hour later I had 200 lines of really readable code!

seats[person[1][:new_Seat_101015]][😡]

If we unpack that statement and resolve it from the inside out, then it will all make sense. The innermost part of it is person[1][:new_Seat_101015]. We are crusing through the list of people using an each loop, the first person we get to is the legendary Matthew Middleton, but seeing as we'll be exposing lots of details lets sit tight and wait until we get to me. This is the entry for me:


  1. 'BenDoherty' => {
  2.                  :surname => 'Doherty',
  3.                  :name => 'Ben',
  4.                  :usedName => 'Ben',
  5.                  :date_hired => '5/07/10',
  6.                  :initial_seat => 'V8',
  7.                  :new_Seat_101015 => 'W3',
  8.                   :title => 'unknown',
  9.                  :Nationality => 'English',
  10.                  :Gender => 'Male',
  11.                  :Glasses => 'FALSE',
  12.                  :Height => 1.8,
  13.                  :image_fileName => 'BenDoherty_0024.jpg'
  14.                 },

If we look for the key :new_Seat_101015 and see what it opens we get 'W3' So this is what that section resolves to:

xCoord = seats['W3'][😡].mm

which is a bit easier to understand. It is asking for the x component of the position of chair w3. This needen’t all make sense right away, it will sink in eventually, and when it does it’ll feel like something from an Indiana Jones film when the sunlight streams into the cave full of treausre and illuminates the perfect ruby in the skeletons eyes.

Ok, take a breather and let that sink in.

OK, that’s enough relaxing, back to work! We need to talk about transformations.

Transformations

Creating the cubes where they actually need to be created is one option, but it'd be much neater to make the cubes at the origin and then move them to where they need to go.  Take a look in the helper module for this function:

def self.cornersOfASquare (edgeLength)
  #this returns a square of points with the bottom left one at 0,0,0
  points = [Geom::Point3d.new(0         , 0,          0),
            Geom::Point3d.new(edgeLength, 0,          0),
            Geom::Point3d.new(edgeLength, edgeLength, 0),
            Geom::Point3d.new(0         , edgeLength, 0)]
  return points
end

You'll notice that the code that makes the face is actually a little bit simpler than it was in lesson 4. We've taken out the bit that relates to the offsets and replaced it in the main code with a transformation.

  1. #make the points
  2. points = BVN_Functions.cornersOfASquare edgeLength
  3. #make a new origin for the square
  4. newOriginOffset = Geom::Point3d.new(xCoord, yCoord, 0)
  5. #make a new transform
  6. myTransform = Geom::Transformation.new newOriginOffset
  7. &#transform the points
  8. for i in (0..3)
  9.   points[i] = myTransform*points[i]
  10. end

operator overloding means that you can do things that you should only be able to do to numbers like 1+1=2 but with operator overloading, we can overload the abilities of the + operator so that we can do stuff that seems obvious, like point1 + point2. We could write a custom operator that gave batman+robin = pow
POW!

Again, there is a lot going on here. Transformation matrices are actually really powerful, but as we are doing a simple transformation so we don’t really need to worry too much about the mechanics. The sketchup API explains how to make a new transformation in this case we are using the overload on the method that takes a point and transforms something by the vector between the origin and the point: "Geom::Transformation.new(pt) creates a Transformation that translates the origin to pt." So we make a point, and then we make a new transformation that takes that point, and then by using the magic of operator overloding we loop through all four points (counting from 0 remember, 0, 1, 2, 3) to transform each one in turn. Then we can make the face on the newly transformed points, and the cube out of the face – POW!

The next big idea that we'll come across as we scan down the frame is a bit of an extention to the defensive programming that we covered in lesson 4. Back then we were trying to make sure that we only let the right type of things past so that they didn't cause trouble, but sometimes it isn't practical to interview for every possible eventuality, so we need an enforcer on the inside who can deal with a problem when one comes up without running to me with every silly problem it comes across.

  1. begin
  2.     materialName = person[1][:image_fileName].split('.')[0]  
  3.     m = materials.add materialName
  4. rescue
  5.     puts 'some sort of strangeness with the image name  ' + person[1][:image_fileName]
  6. end


Remember how we carelessly brushed past the begin and end tags? Well it makes sense, they way that they were at that point they didn’t actually do anything! They were like Salt'n'Pepa, but without Spinderella (and we all know that the back bone of hip hop is the DJ).

What we are missing is a rescue. Rescue is Terry Tate mixed with Florence Nightingale, it is there to catch errors if they show up, handle them in a graceful way, and keep the game moving along. Here's the pattern:

begin
    # the code that you want to run
rescue
    # what to do if it all goes horribly wrong
end

So in our case this tries to name the material name by taking the filename, chopping it up on full stops and then using the first part of the filename as the material name, and then tries to add a new material to the material library.

  1. begin
  2.   materialName = person[1][:image_fileName].split('.')[0]
  3.   m = materials.add materialName
  4. rescue
  5.    puts 'some sort of strangeness with the image name ' + person[1][:image_fileName]
  6. end

If anything goes wrong it pushes a message through to the console telling the user that something has gone wrong, then gets to work on the next thing it has to do. It is important to remember that if you don't tell it to do anything in the rescue section then that's what it'll do, nothing. It is often bad if there is an error, but a rescue allows you to deal with that problem gracefully rather than just blowing up and crashing or going into debug mode.

The last bit draws arcs from people's seat position when the first moved into the office to their new position as of 'the big move' because it leans on the power of the function at the begining of this program it means that all this bit of code needs to do is to make a couple of points and pass them into the function.

Right, that's enough for one day, it's time to go for a lie down in a dark room and let all these ideas float about in your brain and come together like pieces of a cosmic jigsaw puzzle.




Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s