Chapter 8: Modules - Refactoring the engine as a module

Our code for the nose cone is a little unwieldy now, with lots of details. Having lots of details exposed makes it harder for our brain to handle it.

To help mitigate the effects of our tiny brain, we’ll learn how to organize and modularize our code. We do this because:

  1. We want to make the code easier to use.
  2. We want to make the code easier to understand.
  3. We want to make the code easier to construct.

All of this helps keep the code maintainable, because chances are, in the future, YOU’RE going to be the one that has to change it. Do your future self a favor! Make you code clean and readable.

Engine as a basic module

A module is a piece of code that we can write once, and use over and over again. Let’s make the engine into a module. This means we define what an a8_3_engine() means, and then we call it when we want to use it.

Let’s make the engine a module. We can define a module like so:

1 module a8_3_engine() {
2   cylinder(engine_height, engine_radius + tol, engine_radius + tol);
3 }

The name of the module is a8_3_engine(), and the code inbetween the curly braces { is the definition of the module.

And we would be able to call the module by writing:

1 a8_3_engine();

We can put the definition of the module at the top of the file, and call the module where we had called the cylinder before. That means the code now looks like:

 1 ...
 2 
 3 nose_assembly_thickness = 1.2;
 4 nose_assembly_slot_height = 5;
 5 
 6 module a8_3_engine() {
 7   cylinder(engine_height, engine_radius + tol, engine_radius + tol);
 8 }
 9 
10 difference() {
11   // the nose cone
12   ...
13 
14   translate([0, 0, nose_assembly_slot_height]) {
15     mirror([0, 0, 1]) {
16       // the engine
17       // remove the '#' that was in front of the cylinder()
18       a8_3_engine()
19 
20       // the inner dome
21       sphere(engine_radius + tol);
22     }
23   }
24 }

Why use modules?

This change might seems too simple to go to the trouble of doing, but what we’re trying to learn here are the building blocks of how to make our code easier for humans to read and maintain.

Despite what you might think, code is meant to be read by humans, and incidentally executed by computers. Code is also read far more than it is written. Lastly, our brains are limited by how much they can hold.

Just as an industrial designer crafts tools to fit the curve of a human hand for greater comfort and effectiveness, when we write code, we should write it to enhance the strength of the human mind and shore up its weaknesses.

By using modules, we get some advantages that help fit the code to the curve of our minds.What are these advantages?

Our code is easier to use

Which is easier? Driving a car by pushing down the gas pedal with your foot, or timing the firings of the pistons in the engine? It’s the former, because it’s easier to do. We’re much better at moving our foot than at pushing buttons at sub-millisecond intervals regularly.

In the same way, now that we have a reusable module, we can call with a8_3_engine(); over and over again any time we want an engine! No longer will we have to remember to pass in the dimensions of the engine.

This is called abstraction, where we hide the appropriate details of an implementation away from the user of the module. That way, they can concentrate on high-level issues, rather than low-level details.

Our code is easier to understand

When we call modules in our code, we’re effectively communicating our intent to any future readers of the code. Using clear module names, we can semantically understand what’s being built. It’s much easier to see that:

1 a8_3_engine();

means that we’re constructing an engine here. It’s not as obvious when looking at this:

1 cylinder(engine_height, engine_radius + tol, engine_radius + tol);

That’s especially true when you come back to the code after not looking at it for months. And it becomes even more true when the module is much more complicated, with lots of translations, rotations, sphere, and cubes.

When we make it easier for us to read what’s being written, it makes our code more maintainable.

Our code is easier to construct and change

When we use a module to represent an engine, we also make the code easier to maintain.

If in the future, there are changes to what an a8_3_engine looks like, we have exactly one place where we can change what a8_3_engine(); means–in the definition of the a8_3_engine module.

Using modules is better than cutting and pasting cylinder(engine_height, ...) eveywhere we want to create an engine, because if you want to model the engine differently, you now have multiple places in the code that need to be changed. That process can cause headaches and be error prone.

Summary

We learned:

  • How to create a module
  • How to call and use a module
  • Why we would want to use modules

Full Source

 1 //buri.scad
 2 
 3 tol = 0.4;
 4 
 5 engine_radius = 17.5 / 2;
 6 engine_height = 70;
 7 
 8 nose_assembly_thickness = 1.2;
 9 nose_assembly_slot_height = 5;
10 
11 module a8_3_engine() {
12   cylinder(engine_height, engine_radius + tol, engine_radius + tol);
13 }
14 
15 difference() {
16   // the nose cone
17   difference() {
18     scale([1, 1, 3]) {
19       sphere(engine_radius + nose_assembly_thickness);
20     }
21     translate([0, 0, -4 / 2 * engine_radius]) {
22       cube(4 * engine_radius * [1, 1, 1], true);
23     }
24   }
25 
26   translate([0, 0, nose_assembly_slot_height]) {
27     mirror([0, 0, 1]) {
28       // the engine
29       // remove the '#' that was in front of the cylinder()
30       a8_3_engine()
31 
32       // the inner dome
33       sphere(engine_radius + tol);
34     }
35   }
36 }