3D Printing Tutorial: Designing a Micro Drone Frame

Here is a step by step tutorial on how to design objects for 3D printing using OpenSCAD. We illustrate the design process by creating a micro quadcopter frame. This small drone bears the code name of Beatle-1. After following the tutorial you will be able to conceive your own designs for 3D printing.


Contents

  1. 3D Printing Intro
  2. Designing a Drone Frame
  3. Building
  4. Demo

3D Printing Intro

3D printing (known in the industry as additive manufacturing) is a process of fabrication of a 3D object from a digital model. 3D printer prices have significantly dropped during past decade, so 3D printing is much more common nowadays. The reason why we don't see such printers in every household is not economical, but rather psychological. People often prefer ready-made solutions to their problems.

Despite that, maker communities such as Thingiverse provide millions of objects for free. Having access to a 3D printer means that you can download and fabricate things that you would need to buy otherwise. Last Christmas, for example, I have printed pegs to hang Christmas stockings :)

3D printing steps

  • Computer-aided design (CAD)
  • Export to STL model (de-facto standard format)
  • File preparation (slicing) - transforming into the series of commands "understood" by a 3D printer
  • Building ("3D printing")
  • Post-processing (removing fabricated parts from the printing bed, removing support, etc.)

Materials

Plastics, such as PLA, are the most common materials you can use to 3D print objects. While PLA is one of the popular materials to print with, it is also relatively fragile. For increased robustness, it is possible to use e.g. nylon, which like PLA is also compatible with FFF (FDM), the most common 3D printing process. In the industry, there exist different other methods to fabricate objects in metal, carbon fiber, PEEK, and many other materials.

FAQ

If you’ve ever written a simple blog post or email in HTML, you can handle OpenSCAD.

Cubehero

OpenSCAD vs traditional graphic CAD interfaces

Q: Why should I use OpenSCAD?

A: There might be different reasons:

  • OpenSCAD is a great project (and it is open source).
  • You prefer programming to avoid wasting time for repeatable actions. Then, you can learn OpenSCAD in about an hour.
  • Or maybe you have never done any programming, but would not mind to acquire this valuable skill.
  • Your OpenSCAD models are easily parametrizable, virtually out-of-box.

Designing a Drone Frame

Some requirements for the frame I had in mind:

  1. It should accommodate four motors of a given shape.
  2. The motor positions should be stable when flying (they should not wiggle).
  3. The propellers should be far enough from each other.
  4. Easy to print (fast, no support, one or two pieces).
  5. Lightweight frame.

CAD design is an iterative process. My first attempt at designing a drone frame was naive and looked like this.

First version. Don't recommend.

First version. Don't recommend.

A quadcopter's frame is simply a holder for four motors and a body. Right? Wrong. The biggest issue with this design was that the arms were fragile. In fact, one of them cracked while I was removing the newly fabricated frame from printer's platform. Clearly, a better solution was required.

One improvement might be to orient the arms vertically, not horizontally as they are. That would make them more robust in the vertical direction and prevent undesired wiggling. I have also recalled that I have seen a micro quadcopter on Thingiverse some time ago. Indeed, this copter's design suggests a more robust frame. What I like about it is that each motor is supported not by one, but two segments making it more stable in the horizontal plane. So let's see what we can build with OpenSCAD.

Basic Operations: Motor Compartment Design

I like to start my design from individual components (bottom-up), however, the opposite method (top-bottom) is also perfectly valid. For starters, I have created emplacements for F-1607 DC motors, which I have ordered from Olimex. This part can be imagined as a shell around the motor, or a Boolean difference between the external box (gray cylinder) and the motor itself (red cylinder).

A motor holder is simply a difference between two cylinders: the outer (grey) and the inner (highlighted in red).

A motor holder is simply a difference between two cylinders: the outer (grey) and the inner (highlighted in red).

The motor dimensions given by Olimex are 7 mm diameter and 16.5 mm height. I have chosen shell size parameter to be 0.8 mm. I have lifted the inner cylinder by 0.8 mm (translate) so that the result is a box, not a tube. The red transparent cylinder only illustrates where the motor goes, so it will be not visible in the final rendering.

shell=0.8;

difference() {
  cylinder(d=7 + 2 * shell, h=16.5);
  translate([0, 0, shell])
    cylinder(d=7,  h=16.5);
}

How about the motor wires? We can carve out a side and a bottom holes using Boolean difference and union transformations.

A motor compartment: perspective view and top view. The holes will allow motor wires to escape comfortably.

A motor compartment: perspective view and top view. The holes will allow motor wires to escape comfortably.

module SlotF1607(h=16.5,
                 d=7,
                 shell=0.8,  // Shell thickness
                 slack=0.1   // Some extra space to accommodate a motor
                 ) {
    difference() {
       // Motor compartment
       cylinder(d=d + 2 * shell, h=16.5);

       union () {
           // Motor emplacement
           translate([0, 0, shell])
             cylinder(d=d + slack,  h=16.5);

           // Bottom hole for motor contacts/wires
           cylinder(d=6, h=h, center=true);

           // Wires hole
           translate([d/4, 0, 0])
               cube([d, 2.5, h * 3], center=true);
       }
   }
}

Last but not least you can see that I have also given some slack for a motor (0.1 mm) to more easily fit into its compartment.

Placing Motor Compartments

Usually quadcopter size is characterised by its diagonal, i.e. the largest distance between the centers of the propellers (thus the centers of the motors). Our microdrone is small and its diagonal will be only 92 mm (it can fit into a hand). The nearest distance between any two motors is then computed as $\sqrt{2} \cdot (\text{diagonal} / 2)$. This distance is an edge of a square with 92 mm diagonal. Now, we can easily place the four motors on the vertices of the imaginary square.

diagonal = 92;

motors_dist = sqrt(2) * (0.5 * diagonal);

// Half distance between motors
half_dist = 0.5 * motors_dist;

for (i = [1, -1], j = [1, -1]) {
    // Place the motors
    translate([half_dist * i, half_dist * j, 0])
        SlotF1607();
}

Note that for (i = [1, -1], j = [1, -1]) is simply a shortcut of two nested loops, i.e. it is the same as

for (i = [1, -1])
  for (j = [1, -1])
    // ...

Anything wrong? Yes, we forgot to properly rotate the wire outlets.

for (i = [1, -1], j = [1, -1]) {
    // Place the motors
    translate([half_dist * i, half_dist * j, 0])
        rotate([0, 0, -45 * i * j - 90 * j])  // <--
            SlotF1607();
}

Now much better.

Motors Support

We would like to connect the motors using something like an arc. How to do that? First, create and place a small segment:

translate([41, 0, 0])
    square([1.2, 3.6]);

Then, by rotating this segment in space (rotate_extrude), we obtain a 90-degree arc:

module arc() {
    rotate_extrude(angle=90, $fn=70)
        translate([41, 0, 0])
            square([1.2, 3.6]);
}
An arc created with rotate_extrude()

An arc created with rotate_extrude()

Let us link those arcs to the motors.

for (i = [1, -1], j = [1, -1]) {
    // Place the motors
    translate([half_dist * i, half_dist * j, 0])
        rotate([0, 0, -45 * i * j - 90 * j])
            SlotF1607(h=6.5);

    // Connect the motors
    translate([half_dist * j * (i + 1), half_dist * j * (i - 1), 0])
        rotate([0, 0, 45 * i + 90 * j])
            arc();
}

I figured out that probably 6.5 mm slot heigh would be enough. That is why I have specified the height parameter SlotF1607(h=6.5). Works like a charm!

The Platform

By now I have realized that to create the body I could reuse the same method as for the motor emplacements to create an open box, a box with no top cover. However, the only distinction would be the shape (a Boolean difference between rectangular boxes rather than cylinders). Now, I would like to somehow generalize a method of creating an open box. Ideally, I would like to create boxes of any base shape (circle, square, etc.), thus parametrizing the method by a base shape.

To help me with this idea, OpenSCAD provides children() method to access child modules. The new operation could be described as

module openbox(delta=0.8) {
    difference() {
        resize_somehow(...)
            children();
        translate([0,0,delta])
            children();
    }
}

So for example the motor compartment could be created simply as

openbox(h=16.5, delta=shell) circle(d=7);

Where openbox would receive a 2D shape as a child (a circle, a square, or a polygon) and transform it into a 3D shape (a box) of a given height using the recipe from above. So how to transform a 2D circle into a 3D cylinder? Using a common method called linear_extrude:

linear_extrude(16.5) circle(d=7);

This method is similar to rotate_extrude that we used for the arc. However, it does not rotate the base 2D shape.

Now, how to create a larger version of the cylinder (becoming the shell)? One way to do that would be to use scale() or resize(). However, a more efficient method would be to operate on the original 2D object, before extruding it (that is actually the reason why our openbox was assigned to operate on 2D objects). The offset() transformation does exactly what we need to: enlarge the 2D object by a "delta" difference (see documentation). Therefore, the complete openbox module becomes

module openbox(h=10, delta=0.8) {
    difference() {
        linear_extrude(h)
            // Create a larger base by offsetting the child module by delta
            offset(delta=delta)
                children();

        translate([0,0,delta])
            linear_extrude(h)
                children();
    }
}

Create a motor compartment as planned

openbox(h=16.5, delta=0.8) circle(d=7);

Here is how to refactor the SlotF1607 module, making use of openbox

module SlotF1607(h=16.5,     // Module height
                 d=7,        // Motor diameter
                 shell=0.8,  // Shell thickness
                 slack=0.1   // Some extra space to accommodate a motor
                 ) {
        difference() {
            // Motor compartment shell
            openbox(h=h, delta=shell) circle(d=d + slack);

            union () {
                // Bottom hole for motor contacts/wires
                cylinder(d=6, h=h, center=true);

                // Wires hole
                translate([d/4, 0, 0])
                    cube([d, 2.5, h * 3], center=true);
            }
        }
}

Similarly, create the body

openbox(h=3.6, delta=0.8) square([46, 23], center=true);

If it is hard to follow this section, note what we do in the line above is the same operation as with the cylinders in the very beginning:

shell=0.8;

difference() {
  cube([46 + 2 * shell, 23 + 2 * shell, 3.6]);
  translate([0, 0, shell])
    cube([46, 23, 3.6]);
}

We are essentially done. However, this design somewhat does not inspire me. Replacing the base square with a rounded one looks like a good idea. After all, we designed openbox to support any 2D base shape. Here is a common idiom: create a rounded square as a Minkowski sum.

module rounded_square(dim=[20,20], r=5) {
    minkowski() {
        square([dim[0] - 2 * r, dim[1] - 2 * r], center=true);
        circle(r=r);
    }
}

So here we go:

for (i = [1, -1], j = [1, -1]) {
    // Place the motors
    translate([half_dist * i, half_dist * j, 0])
        rotate([0, 0, -45 * i * j - 90 * j])
            SlotF1607(h=6.5);

    // Connect the motors
    translate([half_dist * j * (i + 1), half_dist * j * (i - 1), 0])
        rotate([0, 0, 45 * i + 90 * j])
            arc();
}

openbox(h=3.6, delta=0.8)
    rounded_square([46, 23]);

Much better!

Now, I would like to shave off a bit of weight if possible. So I will introduce some holes as e.g. in the honeycomb pattern. I will generate an array of hexagonal cylinders and subtract it from the platform. A hexagonal cylinder is simply a cylinder with six fragments, i.e. cylinder($fn=6, …);. Here is an array of those generated with two nested loops for (i = [-M:M], j = [-N:N]):

module honey_comb(h=10, M=10, N=4, d1=4, d2=4.6) {
    for (i = [-M:M], j = [-N:N]) {
        // Shift the row by d1/4
        if (abs(i) % 2 == 0)
            translate([i * d1, j * d2 + d1/4, -h])
                cylinder(d = d1, h = h * 2, $fn=6);
        // Shift the row by -d1/4
        else
            translate([i * d1, j * d2 - d1/4, -h])
                cylinder(d = d1, h = h * 2, $fn=6);
    }
}

Where abs(i) % 2 == 0 simply checks if index i is an even value so that odd and even rows are shifted by a different amount.

Now I use an intersection with another shape to limit the span of the honeycomb pattern.

intersection() {
    honey_comb(h=11, M=6, N=4, d1=4.1, d2=4.9);

    // Limited by this volume
    translate([0,0,-0.5])
        linear_extrude(10)
            rounded_square([dim[0]-2.5, dim[1]-2.5], r=6);
}

Finally, I subtract the above pattern and some side holes.

// The platform
module platform(dim=[46, 23], shell=0.8) {
    difference() {
        openbox(h=3.6, delta=0.8) rounded_square(dim);

        union () {
            // Honey-comb patterned floor
            intersection() {
                honey_comb(h=11, M=6, N=4, d1=4.1, d2=4.9);

                // Limited by this volume
                translate([0,0,-0.5])
                    linear_extrude(10)
                        rounded_square([dim[0]-2.5, dim[1]-2.5], r=6);
            }

            // Side holes
            for (i = [-1,1])
                translate([12.5 * i, 0, shell + 4])
                    cube([11, 40, 8], center=true);
        }
    }
}

// The main module
module beatle1() {
    for (i = [1, -1], j = [1, -1]) {
        // Place the motors
        translate([half_dist * i, half_dist * j, 0])
            rotate([0, 0, -45 * i * j - 90 * j])
                SlotF1607(h=6.5);

        // Connect the motors
        translate([half_dist * j * (i + 1), half_dist * j * (i - 1), 0])
            rotate([0, 0, 45 * i + 90 * j])
                arc();
    }

    platform();
}

beatle1();

That was quite easy. Wasn’t it?

I made the complete design available on Thingiverse.


To learn OpenSCAD, check its official cheatsheet. For example, there you will find the magic parameter $fn (number of fragments) which I have used to make the shapes smooth (or conversely to create hexagonal cylinders).

Building

First, export the design as STL. This is quite straightforward: after rendering the design, press and save an .stl file.

Parameters:

  • Fabrication method: FFF
  • Material: PLA
  • 0.2 mm layer height
  • Three perimeters
  • 30 % infill
  • Designed to be fabricated with no support

Next, open your favorite slicer software that often comes with a 3D printer. You will need to specify 0.2 mm layer height and other settings from the list above. After slicing (transforming into a series of commands that 3D printer can perform) the design can be inspected layer by layer.

Sliced Beatle-1. The pink lines (so-called skirt) will be printed first to stabilize the flow of plastic.

Sliced Beatle-1. The pink lines (so-called skirt) will be printed first to stabilize the flow of plastic.

Demo

I used a set of four F-1607 DC motors with propellers, a flight controller from a drone toy, and a 150 mAh LiPo battery.

Some of the Challenges I Have Faced

  • The design from the very first version was impractical and fragile.
  • Motor wires could be easily detached from the controller. To prevent this, I fixated the wires to the frame.
  • I tried several methods to create the openbox module. The one presented here was the least ambiguous.

As always, if you have any questions, remarks, or spotted any typos please send me a message.

Learn More

Related