Lesson 3

As promised in lesson 2, this week we're going to make a whole bunch of boxes, each one with a different face on them. The code is an extention of last week's lesson, so there should be some familliar stuff in there, and some new stuff. In true Blue Peter style, Here’s one I made earlier, try to read it:

  1. #setup stuff
  2. mod = Sketchup.active_model
  3. ent = mod.entities
  4. sel = mod.selection
  5. materials = mod.materials
  6.  
  7. #some cube variables
  8. edgeLength = 1000.mm
  9. gap = 100.mm
  10. offset = 0
  11.  
  12. #tell ruby where our textures are
  13. Dir.chdir (‘C:\Users\bdoherty\Desktop\BVN_Photos‘)
  14.  
  15. Dir.foreach(“.“) { |file|
  16.   type = file.split(‘.‘)[1]
  17.   unless type == nil
  18.     type.downcase!
  19.     if (type ==’png‘) or (type==’jpg‘)
  20.       puts file
  21.  
  22.       #create a cube
  23.       #setup the corner points
  24.       #                         x                    y           z
  25.       points=[Geom::Point3d.new(0 + offset,          0,          0),
  26.               Geom::Point3d.new(edgeLength + offset, 0,          0),
  27.               Geom::Point3d.new(edgeLength + offset, edgeLength, 0),
  28.               Geom::Point3d.new(0 + offset,          edgeLength, 0)]
  29.  
  30.       #add the bottom face
  31.       myCubeBase = ent.add_face points
  32.       myCubeBase.reverse! #flip it.
  33.       #the ! means -in place- i.e. myCubeBase = myCubeBase.reverse
  34.   
  35.       # makes a user friendly name for the material by removing
  36.       # the file extention. '.'+type handles .png and .jpg
  37.       materialName = file.chomp(‘.‘ + type)
  38.       # Adds a material to the “in-use” material pallet.
  39.       m = materials.add materialName
  40.       # pulls a texture out of a folder
  41.       m.texture = file
  42.       unless m.texture == nil
  43.         m.texture.size = edgeLength
  44.       end
  45.       myCubeBase.material = m
  46.       #do a push pull to make it into a cube**********
  47.       myCube = myCubeBase.pushpull(edgeLength, true)
  48.       offset += edgeLength + gap
  49.     end
  50.   end
  51. }

How did that feel? Are you starting to be able to read the code? You aren’t just learning to program, you are aquiring a new kind of literacy! (That’s a pretty big and exciting idea!)

Even to the untrained eye, there is a bit more going on here. Not a huge amount, the code in part 1 was 39 lines long, and the code in this part is 52, not an encyclopedia of code more, so lets unpick what it does.

Line 10 is the first time that anything new happens. There is a new variable that defines the gap between the boxes as we go along. With each new box we draw, we add 1 box width and 1 gap to the place that we start drawing the box. This is so we don’t just draw all the boxes on top of each other. The first time around the loop the offset from the origin is nothing, so we define offset = 0.

Now there is a furious flurry of activity, lines 14 to 21 are doing a lot of work.

  1. Dir.chdir ('C:\Users\bdoherty\Desktop\BVN_Photos')

Line 14 sets the currently active directory to be wherever you are hiding your pile of images. It is a method on the Dir class so if you want to read about it then it is in the docs. This is like the conductor raising his baton, and the moment of silence becore the cacophany starts.

foreach loops

  1. Dir.foreach(".") { |file|

Line 16 is a sneaky one, it really gets things going. Dir.foreach(".") is saying to ruby “for each thing in the current directory”. "." is old fashioned

speak for this directory “. Ahem, as I was saying, for each thing in the current directory, throw out the name of that thing

I was camping once, and there was a noise outside the tent, on poking my head out I could see that it was a wombat. He had prised the lid off our food tub, and had climbed inside. He was throwing the food out to his acomplice who was then ferrying it off to their burrow. This part (Dir.foreach(".")) of the foreach loop is like the wombat in the food tub.

If the wombat in the food tub is getting all the glory, the next bit is doing all the work. { |file| catches the filename, calls it file while it is holding it, and runs off into the burrow.

The burrow is defined by the curley braces {} they even look a bit like a burrow! While the filename is in the burrow we can call it file, but not when it is outside, ruby doesn’t think it exists outside. This is more scope. (Remember scope from the last lesson?) 

Lets follow our industrious wombat sidekick down the burrow, and see what’s inside… 


Actually, I lied a little bit when I said that the wombat in the tub was the one who did the renaming. As _Why puts it, |file| is like a chute that is just inside the burrow.

Imagine Alice falling down the rabbit hole. In Lewis Carrol’s version she is Alice when she goes in, and Alice when she gets to the bottom, but in our version there is a sign at the top of the rabbit hole that reads “become file“. Then as we are falling down the hole we reach into the cupboards and draws that surround us and pull out the f hat, the i tie, and the le jumpsuit. The inhabitants of wonderland now thing that we are the famous Mr file, and not just an imposter dressed as him.

So now we are dressed as file we need to pass some more tests. This is called defensive programming; it is important never to underestimate how many ways that programs can go wrong when you feed them duff information. Defensive programming is a bit like a border guard, checking your papers, then sending you on for an interview, then doing a retina scan, an xray, calling your references, doing an aptitude test…

In this case the '.' is the delimeter. If we were to split on  'e' then we'd get the arrays ["britn", "ynak", "d.txt"] and ["mThatch", "r_BrightonB", "ach.jpg"]

type = file.split('.')[1] is the first stage, it is like taking your laptop out of your bag at the airport. Because file is a string it can be fiddled with by the string methods as we played with in the first lesson. Split takes a file name like britneynaked.txt or mThatcher_BrightonBeach.jpg and if we ask it to split* on ‘.’ we get the arrays ['britneynaked', 'txt'] and ['mThatcher_BrightonBeach', 'jpg]. So because arrays are indexed from 0 we know that the file extention is in the 1st box. We take that information, and store it in type.

unless

Ruby’s unless blocks really are a thing of wonder. They are a part of ruby’s unparallelled readability. Try it, read it out loud: “unless type equals nil do this stuff”. What unless type == nil does is make sure that the there really is something to check for hidden inside the type.downcase! is like the helpful people in the customs queue who tell you where to stand and what to say before you get interviewed by the big scary if block. It does an in-place converstion from aNy CaSE to all lowercase. That way we can be sure that 'apples'=='apples' because ‘APPLES’ does not equal ‘apples’.

'apples'=='apples' → true

'APPLES'=='apples' → false

more conditionals

if (type == 'png') or (type == 'jpg')

The next line checks to see if type is either a .png or a .jpg. It is a conditional statement so it must eventualy resolve to a single true or false value. This is something that caught me out so may times when I was learning to program; I would always write if (type == 'png' or 'jpg') but as far as the computer is concerned this is meaningless. It helps to think of this as a tree.

Each bracket resolves to a true or false value, and then that gets resolved, and eventually we end up with a single value. A true or false value is called a bool after the 19th-centuary mathmatician and philosopher George Boole. He invented a kind of mathematical logic that was only concerned with truth values. There are other types of logic that have more than two values, but they are pretty hardcore – fuzzy and ternary to name a couple.

So after that excursion, we have made a check that the file that we are dealing with is in fact a png or a jpg. Our defensive programing won’t allow things that fail that test through, so we have a better chance that there will be fewer errors within that block.

Point objects

An object is a special programing concept, very similar indeded to its real world counterpart. In simple terms, objects have methods and properties. Methods are things that the object can do, and preoperties are facts about the object. Instantiate means that you create an instance of an object. So in this case when we instantiate a new point, we make a new point object.  

The bit where we define the corner points of the face has changed a little bit here too. Instead of using the lightweight point substiture of a three element array i.e. [xValue, yValue, zValue]. This time we are going to instantiate a point object. →

Geom::Point3d.new(xValue, yValue, zValue)

Point3d is an object in the Geom class. We won’t go into the details of what all of this means, all that matters is that it has a method called .new that makes it make a new one using the arguments provided. This line does the same thing as it did before, except this time we are instantiating the points more explicitly. This doesn’t do much from the computer’s point of view (it would be useful if we were assigning them to a variable because we could refer to myPoint.x rather than myArrayMasqueradingAsAPoint[0]) but its real use is that it makes the code readable by making it explicit what the code is doing.

The next line that is different is the one where the material gets named:

materialName = file.chomp('.jpg')

So what is going on here is that we call up the power of the chomp method (which belongs to the string class) by dangling a bit of bloody tuna carved into the shape of the word ‘.jpg‘ off the back of the plane, and waiting for the great white shark of string to come and chomp it off. We could carve anything into the argument, so if I wanted to chomp ‘plane‘ off the end of ‘aeroplane‘ then I would say ‘aeroplane'.chomp('plane') and it would give me back 'aero'. Using the chomp method gives us the name of the person in the picture, without the file extention.

We’ve seen unless before, so We can skip that and move straight down to line 48.

Counters

Remember that we are doing the stuff between the {} braces lots of times; once for each file in the folder. This means that we can use this process to change things outside the loop each time we go around.

Photo by David Lee / Rex Features ( 982851j ) 99 Flake Various - 2009Imagine that we are on a roundabout, and each time we pass the start we lean out and take an ice cream. Now imagine really really hard (because this part is totally unbelievable) that you don’t like flakes.

If you pulled out all the flakes and stacked them on the seat beside you, after a while you’d have a lot of flakes. One for each time you’d been around.

What we are doing is that each time we go around we are adding a number (in this case two numbers; gap and edgeLength) to the current offset. This means that the next time that offset is used it will be a bit bigger than last time round, and so on until there are no more photos to make boxes out of. This works because offset is defined outside the scope of the the loop. If it were to be defined inside the loop then it'd be overwritten each time we passed it.

some faces mapped onto boxes

And that, dearly beloved, is how to make a lot of boxes with faces on them. Take a deep breath, and lets dive straight back in!

Refactoring

At the moment we have a big chunk of code; it doesn't even do very much yet! It's quite hard to read it all in one go and to understand it, so imagine what it would be like to read a really big program. The way that we get around that and make our code managable is to refactor the code. This means that we take the big block, and break it up into sections. Either conceptual sections or functional sections.

This is a hard concept in the abstract, but it's easy if we talk about something that we're comfortable with. Take a look at this super tasty breakfast from the Jamie Oliver website!

kedgeree
© David Loftus

kedgeree

main courses | serves 6

This is a traditional British breakfast from colonial India and it’s a lovely little dish, with a nice balance of spicy and smoky flavours. It makes a tasty lunch or supper too – so get stuck in!

Boil the eggs for 10 minutes, then hold under cold running water. Put the fish and bay leaves in a shallow pan with enough water to cover. Bring to the boil, cover and simmer for about 5 minutes, until cooked through. Remove from pan and leave to cool. Remove the skin from fish, flake into chunks and set aside.

Cook the rice in salted water for about 10 minutes and drain. Refresh in cold water, drain again, and leave in the fridge until needed. Melt the butterghee in a pan over a low heat. Add the ginger, onion and garlic. Soften for about 5 minutes, then add the curry powder and mustard seeds. Cook for a further few minutes, then add the chopped tomatoes and lemon juice.

Quarter the eggs. Add the fish and rice to a pan and gently heat through. Add the eggs, most of the coriander and the chilli and stir gently. Place in a warm serving dish. Mix the rest of the coriander into the yoghurt and serve with the kedgeree.

ingredients

  • 2 large free-range or organic eggs
  • 680g undyed smoked haddock fillets, pinboned
  • 2 fresh bay leaves
  • 170g long grain or basmati rice
  • sea salt
  • 110 pure butterghee
  • a thumb-sized piece of fresh ginger, peeled and grated
  • 1 medium onion or 1 bunch of spring onions, finely chopped
  • 1 clove of garlic, peeled and finely chopped
  • 2 heaped tablespoons curry powder
  • 1 tablespoon mustard seeds
  • 2 tomatoes, deseeded and chopped
  • juice of 2 lemons
  • 2 good handfuls of fresh coriander, leaves picked and chopped
  • 1 fresh red chilli, finely chopped
  • a small pot of natural yoghurt

Apart from being one of my favourite brekfasts, this recipe makes extensive use of refactoring. The first line asks you to:

"Boil the eggs for 10 minutes, then hold under cold running water"

How on earth do I know what boil means? What is hold under water? We could rewrite this as Boil(eggs, 10) and HoldUnderWater(eggs, cold) to bring us back into the code world. Knowing how to boil something is assumed prior experience, probably learnt from Delia Smith or from even earlier in life. Whenever a recipe calls for us to boil something we can recall this extra set of instructions and use them. When we're done with the boiling we can return to the main set of instructions and carry on.

Functions are a way of making code reusable. Imagine trying to explain to an incredibly meticulous, but not at all bright person how to do something, this time lets say bake a cake (this example comes up all the time). To someone who knew about baking cakes all you'd need to do is provide the list of ingredients and a cooking time, their experience would do the rest. If we were teaching a young child about baking cakes we'd need to go into a lot more detail, we'd need to explain how to sift flour, how to break eggs, how to stir in a mixing bowl, the list goes on.

Computers are even less useful than children, they know nothing, but luckily they have been shown how to do some things (like adding up numbers and that kind of thing) by their designers, and then people have written progressively more compicated instruction sets and put them into libraries. Each time we called ent.add_face points we were calling on one of these library functions.

To stretch this analogy even further; to a recipe for making cakes starting with 'tense thenar muscle', this is a bit impractical, so the next cookery book would have a recipe for how to break an egg that would explain in detail how that was done, and then each time an egg needed to be broken it could say "break an egg (see p12)". at a stroke the cookery book gets 99% thinner as all of the repetitive tasks get refactored into other places.

Some functions are simple, like turn_on_oven_and_preheat(180) this has an effect, but doesn’t return anything, and some are a little more complicated, like sift(flour) which returns some sifted flour, and gets rid of all the weevels and grit. The thing in the bracket is an argument, you can pass in lots of arguments because these are what the functions use to decide what to do. You don't actuall need brackets in ruby, so it is up to you if you want to use them to improve clarity and readability.

def addNumbers(firstNumber, secondNumber)
    newNumber = firstNumber + secondNumber
    return newNumber
end

This is a pretty simple function, it takes two numbers, adds them together, and then gives back the answer. def is a keyword that tells the computer that they thing coming up is going to be a function, then the next word (addNumbers) is the name of the function, then the arguments in brackets.

def addNumbers(firstNumber, secondNumber)
    …
end

The bit between the def and the end is where the magic happens. Ruby Has some lightweight ways of doing functions, but I like to have a return statement when the function returns something, and leave it off when it doesn't, I just find it easier to read.

Refactoring in action

You can get into trouble here, but that trouble can lead you to understanding your code a bit better too; you need to take a look at the chunk of code that you want to take out and isolate the things that go in and out of it.

  1. Dir.foreach(".") { |file|
  2.  type = file.split('.')[1]
  3.  unless type == nil
  4.   type.downcase!
  5.   if (type =='png') or (type=='jpg')
  6.     puts file
  7.      draw_a_cube(edgeLength, offset, file, ent, materials)
  8.      offset += edgeLength + gap
  9.   end
  10.  end
  11. }

That’s pretty simple isn't it! just take out the painful bit in the middle and use the pre-defined draw_a_cube function. Well, it is once the function is defined. the tricky bit comes when you need to define the function. Remember, this takes the format:

def function_name(arguments)
 #the code that you need to write
end

def is the begining of the definition of the function, with it's matching end tag, then you get to flex your creative juices and make up a good name for your function. Unfortunatley chomp is taken so you might as well come up with something useful that describes what the function actually does. The bit after that is where we pass in the variables, the arguments section.

Below is the function that draws the cube. Take a look at the code and try to read through it to see where it seems different to what we have already.

def draw_a_cube(edgeLength, 
                offset, 
                materialFileName, 
                entityListToAddTo, 
                materialListToAddTo)
  type = materialFileName.split('.')[1]
  #create a cube
  #setup the corner points
  # x y z
  points=[Geom::Point3d.new(0 + offset,          0,          0),
          Geom::Point3d.new(edgeLength + offset, 0,          0),
          Geom::Point3d.new(edgeLength + offset, edgeLength, 0),
          Geom::Point3d.new(0 + offset,          edgeLength, 0)]

  #add the bottom face
  myCubeBase = entityListToAddTo.add_face points
  myCubeBase.reverse! #flip it.
  #the ! means -in place- i.e. myCubeBase = myCubeBase.reverse

  # makes a user friendly name for the material by removing
  # the file extention. '.'+type handles .png and .jpg
  materialName = materialFileName.chomp('.' + type)
  # Adds a material to the "in-use" material pallet.
  m = materialListToAddTo.add materialName
  # pulls a texture out of a folder
  m.texture = materialFileName
  unless m.texture == nil
    m.texture.size = edgeLength
  end
  myCubeBase.material = m
  #do a push pull to make it into a cube
  myCube = myCubeBase.pushpull(edgeLength, true)
end

There isn't that much that's different, but the subtleties will probably catch you out if you don't have laser concentration. The main difference is related to our old friend scope. Once you are inside the block that defines the function defend your scope gets reset. Variables that are defined in a diferent block are no longer available unless you pass them in through the arguments list. This means that we can reuse the variable names edgeLength and offset because they are in a different scope. The other thing that's worth bearing in mind is that because we are in a different scope and things that are available outside are unavailable, we need to either redefine type or pass it in through the args; in this case, type is a derivitave of materialFileName so it makes more sense to derive it inside than to pass it in and risk errors from passing in one file name and another file extention by accident.

The only other thing to note is that for the moment the function needs to be defined before it is called for the first time. To put it very simply, that means that your main bit of code needs to be at the bottom for the moment.

It'd be a bit too much like hard work to put all the code here, but look at this ruby file to see how the code is structured now that it's been refactored a little bit.

Homework

There are plenty of other oportunities for refactoring in this code. Firstly try to edit the code to use this function:

def SimpleSquareFace(edgeLength, offset, entityListToAddTo)
  points=[Geom::Point3d.new(0 + offset,          0,          0),
          Geom::Point3d.new(edgeLength + offset, 0,          0),
          Geom::Point3d.new(edgeLength + offset, edgeLength, 0),
          Geom::Point3d.new(0 + offset,          edgeLength, 0)]
  myCubeBase = entityListToAddTo.add_face points
  myCubeBase.reverse!
  return myCubeBase
end

This week's homework is in two parts:

  1. Try to implement the SimpleSquareFace function, but if you get really stuck you can see how to do it here.
  2. Refactor something else in the code. (Hint There is some vaule in looking at the materials section of the draw_a_cube function)

Leave a comment