Creating cookie cutters using offsets (and minkowski sums) in OpenSCAD to make your honey happy

Last time, we looked at how to organize your OpenSCAD code. This time, we’ll get a little bit more advanced. We’re going to make cookie cutters! And in the process, we’ll learn about linear extrusion, Minkowski Sum, and how to do offsets in OpenSCAD.

Let’s say it’s Christmas time, and you want to make your honey happy. She loves chess and you decide to make her some knight-shaped cookie cutters. But your local Crate and Barrel (a home goods store in the US) isn’t going to carry such specialized cookie cutters.

Time to roll up your sleeves and make your own.

First, we make an outline of a knight profile in any vector drawing program that exports to DXF, like Inkscape. I won’t cover that process here, since it’s beyond the scope of this guide, but you can download a pre-made one here (just click the download button).

However, note that if you decide to draw your own DXF file, you’ll need to install a plugin for Inkscape that will export to a DXF format. Once we have our DXF file, we can import it into OpenSCAD pretty easily with the import function.

import(file="knight_profile.dxf");

Notice here that it’s still a 2D shape. In order to make it a 3D shape, we’re going to extrude it vertically. Linear extrude is like stretching and projecting a shape in the z-direction. It gets its name from the mechanical process of making a long shape with the same cross-sectional profile.

In OpenSCAD, this is also pretty easy. Linear extrude has many more options, but the important one to pay attention to is the height. It dictates how far off the XY-plane we should extrude. We’ll wrap it all in a module called knight_profile().

module knight_profile() {
  linear_extrude(height = 5)
    import(file="knight_profile.dxf");
}

Now that we have a 3D extrusion, it’s still not usable as a cookie cutter, as there’s no actual hole to cut out our cookie. At best, we merely have a stamp. So our task now is to punch a hole with the exact same outline, but smaller inside of our knight-shaped extrusion.

Offset

What we need is called an “offset” in other types of CAD packages. We would like an expanded outline of our extruded shape. When we’d like a shrunken outline of our extruded shape, it’s called an inset.

offset_cover

We can see here that on the left is the original extruded shape. The middle is the offset shell. The right-most shape is the inset shell.

Well, there’s no OpenSCAD function called offset or inset. What to do? We build our own from OpenSCAD’s primitives! This is what I meant by building abstractions, where we can think about manipulating our model at a higher level.

One way we could do this is to find the center of mass of our shape, and then scale the knight in the X and Y direction, then difference that shape. However, calculating the center of mass is not easy for arbitrary shapes, and only done for the simplest of shapes. In addition, that method wouldn’t work for concave shapes.

Building offset with the Minkowski Sum

The secret to building offset is to use the Minkowski Sum! But what’s a Minkowski Sum? That bears a bit of explaining. The Minkowski sum is a way to integrate two shapes together, by using the second shape to trace the surface of the first, and union-ing all the places the second shape had been while tracing.

Example time! If we look at an example of applying Minkowski to a Square and a Circle, we can see that when we imagine using the circle as a “pen”, and tracing the outline of the square.

minkowski_2d_example1

When we look at the unioned result, we get a rounded square. That is our Minkowski Sum in two dimensions.

minkowski_2d_example2

In three dimensions, when we apply the Minkowski Sum to a cube and a sphere, we can imagine the sphere as a “pen”, and tracing the surface of the cube.

minkowski_3d_example1

When we look at the resulting Minkowski, we get a cube with rounded corners.

minkowski_3d_example2

Note that the minkowski sum is commutative, meaning it doesn’t matter if you think of the square or circle as the “pen” doing the tracing. The resulting sum is the same.

By now, you should realize that an offset is just the minkowski sum of our extruded knight profile with a cube! And then to get our cookie cutter, we can difference the offset with the original extrusion. Note that we didn’t need to calculate the center of mass, and this works on any arbitrary shape, translated anywhere in space. So let’s code it up!

module knight_profile() {
  linear_extrude(height = 5)
    import(file = "knight_profile.dxf");
}

difference() {
  render() { 
    minkowski() {
      knight_profile();
      cube(center=true);
    }
  }
  translate([0, 0, -1]) scale([1, 1, 1.5])
    knight_profile();
}

Now, the Minkowski can take a long time to compute. As such, that’s why we used a cube, so there’s as little faces to process as possible while tracing the original shape. In addition, you can wrap the minkowski() in a render(), to cache the results, so OpenSCAD doesn’t have to compute it every time something changes in the file, outside of the render() block.

Lastly, we scale the knight extrusion by 1.5 in the z direction and then translate it by -1 to get rid of co-incident planes that results in shimmering. And we now get the offset shell of the knight!

offset_shell

Making our solution reusable

However, our solution isn’t reusable, let’s put it in a module called offset_shell() that takes a single parameter controlling the thickness of the offset.

module offset_shell(thickness = 0.5) {
  difference() {
    render() {
      minkowski() {
        children();
        cube([2 * thickness, 2 * thickness, 2 * thickness], center=true);
      }
    }
    translate([0, 0, -5 * thickness]) scale([1, 1, 100])
      children();
  }
}

Note the biggest changes are the adjustments to the thickness of the offset, and the use of the children() function, which allow us to operate on modules nested within offset_shell(). And we can use it like so:

offset_shell(1)
  knight_profile();

Whenever you see children() in the code, it refers to the resulting shape of the children of offset_shell(). In this case, it refers to the knight_profile(). Instead of writing it yourself, I’ve put it up in a library on Cubehero, so you can use it yourself.

The library’s README.md has more explicit and up to date instructions on how to use it. Now our offset is abstracted, and we don’t have to worry about any of the details again!

Insets

So what about insets? That is a little trickier, as there’s no Minkowski Subtraction operation–akin to using the second shape to cut from the first shape while tracing the surface first shape. However, there’s a trick we can employ. First, we can invert our knight_profile (turn it inside out), by differencing a very large cube with our extruded knight_profile.

When you run the code below, you won’t see the picture the follows. But I faked it so you can get a sense of what it’s doing. I show a smaller transparent cube here in the picture for illustrative purposes. The red signifies a hole.

module invert(bbox = [5000, 5000, 5000]) {
  difference() {
    cube(bbox, true);
    children();
  }        
}

invert() knight_profile();

invert_shape

So we get a huge cube with a knight_profile shaped cave inside of it. If we run the Minkowski on that, we’d get the shape of the inverted inset! That’s because the minkowski traces the entire surface, regardless of whether it’s on the outside or inside.

minkowski() {
  invert() knight_profile();
  cube([1, 1, 1], center=true);
}

inverted_inset

Now that we have the inverted inset, we want the inset. To get the inset however, we now need to turn the inverted inset right-side out. We can do that by subtracting the inverted inset from a smaller bounding box.

invert(0.9 * bbox)
  minkowski() {
    invert() knight_profile();
    cube([1, 1, 1], center=true);
  }

inset And now, to get the inset shell, we cut the inset shape from the original extruded knight, by expanding it in the z direction and shifting it downwards to make sure there aren’t any co-planar surfaces. Again, the inset is represented in red as a hole.

difference() {
  children();
  translate([0, 0, -5]) scale([1, 1, 100])
    translate([0, 0, -2])
      invert(0.9 * bbox)
        minkowski() {
          invert() knight_profile();
          cube([1, 1, 1], center=true);
        }
}

inset_shell Now, we’ll refactor it all so that it’s readable, and useable without worry about the low-level details:

module inset_shell(thickness = 0.5, bbox = [5000, 5000, 5000]) {
  module invert(bbox = [5000, 5000, 5000]) {
    difference() {
      cube(bbox, true);
      children();
    }        
  }

  module inset(thickness = 0.5, bbox = [5000, 5000, 5000]) {
    render() {
      invert(0.9 * bbox)
        minkowski() {
          invert() children();
          cube([2 * thickness, 2 * thickness, 2 * thickness], center=true);
        }
    }
  }

  render() {
    difference() {
      children();
      translate([0, 0, -5 * thickness]) scale([1, 1, 100]) translate([0, 0, -2 * thickness])
        inset(thickness, bbox)
          children();
    }
  }
}

inset_shell(1)
  knight();

inset_shell

Again, you don’t have to write everything yourself. I’ve wrapped it all in a library for you to use. A couple things to notice:

First, by default, this only works with shapes that are smaller than 5000x5000x5000. In OpenSCAD, there isn’t an easy and fast way to programmatically the bounding box of a shape. (It does exist, but it’s damn slow). That’s why we allow the user to change the default size on the bounding box. But most of the time, the default will work nicely and won’t need to be specified.

Second, we wrap the inset shell with a render, because otherwise the result won’t render correctly on the screen. OpenSCAD takes shortcuts to render CSG results fast (when you render with F5), but there are edge cases where it doesn’t work and you need to do the full CSG computation (when you render with F6).

Lastly, modules can be nested. The invert module and inset module is nested inside of the inset_shell module. That means modules outside of inset cannot use invert or inset. But in this case, we use it to make our code more readable.

For the more astute amongst you, you may have noticed you can skip the second inversion step and just intersect the inverted inset with the original extrusion to get the inset shell. And you’d be right! But I couldn’t get it to work with different inset thicknesses. If you have patches to submit, I’d be happy to incorporate it. Make sure you try it with different thicknesses from 0.1 to 20.

In Summary

Now your honey is happy with her chess pieces, and you find some other way to make the holidays bright. Offsets and insets are easy to do in OpenSCAD once you know about the Minkowski Sum. The library for offsets from this blog post is hosted on Cubehero.

Let me know that you find these helpful by signing up for my mailing list to get more OpenSCAD guides as I write them. It’ll motivate me to write more! See what I’ve written previously on OpenSCAD. If you’d like to see what else I work on, check out Cubehero, or follow me on twitter.

Tagged with: , , , ,
Posted in openscad, tutorial

Organizing your OpenSCAD code: Part I

So you know the 10 things to be dangerous in OpenSCAD, and now you have the power to easily 3D model in your hands! However, for all but the simplest of shapes, OpenSCAD code can quickly become a tangled mess of symbols, curly braces, and numbers.

Missed out on previous OpenSCAD tutorials? See what I’ve written previously on OpenSCAD.

But organizing? Psha! Who has time for organizing? Actually, learning how to organize your code will help you make more complicated shapes and assemblies, while being productive. I’ll introduce a few hard-won principles programmers in other languages have learned over the years. Interspersed with the principles, we’ll go over actual OpenSCAD statements to teach you how to organize your code.

It’s harder to read code than to write it.

Early on in your project, you start writing a flurry of code to model your part, and you’re really productive! But then no matter how smart you are, there’s only so much you can fit in your head at once. Pretty soon, you’re confused as to which part of the code produces which part of the model. And it seems like changing one part has cascading changes to the rest of the assembly.

And it’s even worse when you come back a couple weeks later to look at your code. You can’t remember where you put the origin, what order the parameters are in, or why you modeled things that way.

So always write code to be easier for the maintainer, because chances are, that person will be you in the future. And you will hate yourself if you don’t!

Using modules to reuse code

One way to organize your code is to make reusable components. Not only will we save on typing and cutting and pasting, but more importantly, we will have a single implementation in one place. So if we need to change something, we only need to change it in one place, and not hunt for all the copies of something.

Let’s say we want to make a ring. We can do this by subtracting a cylinder from another cylinder.

difference() {
  cylinder(h = 2, r = 10);
  translate([0, 0, -1])
    cylinder(h = 4, r = 8);
}

ring with cylinder

But what if we want to make a clover patterned cup coaster from multiple rings? Well, we can translate the ring in different directions and get:

translate([10, 0, 0])
  difference() {
    cylinder(h = 2, r = 10);
    translate([0, 0, -1])
      cylinder(h = 4, r = 8);
  }
translate([-10, 0, 0])
  difference() {
    cylinder(h = 2, r = 10);
    translate([0, 0, -1])
      cylinder(h = 4, r = 8);
  }
translate([0, 10, 0])
  difference() {
    cylinder(h = 2, r = 10);
    translate([0, 0, -1])
      cylinder(h = 4, r = 8);
  }
translate([0, -10, 0])
  difference() {
    cylinder(h = 2, r = 10);
    translate([0, 0, -1])
      cylinder(h = 4, r = 8);
  }

clover

While it works, the code is terrible. If we then decided we wanted rings of a different height, we’d have to change it in four different places. Let’s make the ring a reusable component with the module() statement:

module ring() {
  difference() {
    cylinder(h = 2, r = 10);
    translate([0, 0, -1])
      cylinder(h = 4, r = 8);
  }
}

Now we can reuse the ring component for our clover coaster:

translate([10, 0, 0]) ring();
translate([-10, 0, 0]) ring();
translate([0, 10, 0]) ring();
translate([0, -10, 0]) ring();

Much better! But what if we want to use this over and over again in difference sizes? It’s not as reusable if we can’t parameterize the ring() module for rings of different sizes.

Parameterized modules can help make code reusable

Currently, our ring() module can only generate one kind of ring. To leverage the power of modules, we should parameterize our ring module so we can make different rings of different sizes.

What aspects of our module should be parameterized? It’s situation dependent, but usually it’s aspects of the module that a user might want to change. Our first guess may be that it’s the height of the ring (height), the inner radius (inner_radius), and outer radius (outer_radius).

module ring(height, inner_radius, outer_radius) {
  difference() {
    cylinder(h = height, r = outer_radius);
    translate([0, 0, -1])
      cylinder(h = height + 2, r = inner_radius);
  }
}

ring_inner_outer

Notice that we chose the parameters according to how it was constructed. This is as bad of an idea as ice cream on pizza!

When we have parameterized modules, we want to make it easy to use, to encourage reusability. Hence, we want to hide how a module does its work or constructs the shape, because a module is easier to use if we can tell it what to do without knowing how it does it. As a result, we shouldn’t have to provide parameters for a module that requires us to know how a shape was constructed.

A more natural way to think about a ring would be to change the radius from the center to middle of the ring (radius), the radial thickness of the ring (radial_width), and the vertical thickness of the ring (height). In that case, our module would be:

module ring(height, radius, radial_width) {
  difference() {
    cylinder(h = height, r = radius + radial_width / 2);
    translate([0, 0, -1])
      cylinder(h = height + 2, r = radius - radial_width / 2);
  }
}

ring_radial_wdith

This is more apparent the more complicated shape a module produces. Remember to make the parameters of a module so that when you’re using it, you don’t have to look into how a module is implemented in order to figure out how to use it! That leads us to our next principle.

Keep interfaces steady, and hide implementation details

When you don’t need to look into how a module does something in order to ask it to do that thing will free your mind to think about the other things that need to be done. That will speed up your development and make the module fun and a breeze to use.

Going back to the ring module, it shouldn’t matter if I used a difference of cylinders or a rotational extrusion to make the ring. The following two modules produce exactly the same shape with the same interface:

// ring implemented with cylinders
module ring(height, radius, radial_width) {
  difference() {
    cylinder(h = height, r = radius + radial_width / 2);
    translate([0, 0, -1])
      cylinder(h = height + 2, r = radius - radial_width / 2);
  }
}
// ring implemented with rotational extrusion
module ring(height, radius, radial_width) {
  rotate_extrude()
    translate([radius, 0, 0])
      square([radial_width, height]);
}

As a user of the module, we shouldn’t care how it was implemented, we should care that the interface isn’t subject to change. In the future, let’s say we find that one way of generating the ring is faster than another. When we have a stable interface, we can change the implementation without cascading changes to the rest of the code that uses the ring module.

Use succinct descriptive module names

There are other things one can do to make modules easier to reuse. The modules themselves should have names that make sense–that are both descriptive, yet succinct.

module ring2()

doesn’t tell me anything about what it does other than it’s not ring(). But if it was named:

module ring_rounded()

Then I can at least tell it’s a rounded ring (which by the way is called a torus).

Principle of Least Astonishment: Be consistent in your interfaces

Part of making modules easy to use is to have consistent interfaces. The more consistent your interfaces for your module are, the less you’ll have to keep in your head. So if earlier, we used the interface:

module ring(height, radial_width, radius)

when we write ring_rounded, we should use the same names in the same order

module ring_rounded(height, radial_width, radius)

That way, when we use ring_rounded, we’re not wondering…does the height go first? Or does the radial width? The less we have to worry about those types of details, the better.

Of course, it’s all a matter of degree. Based on this, we can argue that we should also be consistent with OpenSCAD’s core cylinder’s conventions, so our module interface should be:

module ring(h, rw, r)

However, that’s a balance between consistency and readability. That’s something for you to decide.

Code reuse leads to abstractions

When we have code we can reuse, we have modules we can tell them what to do without knowing how they do it. That’s what’s called an abstraction. Abstractions free up our mind to focus on higher level details, instead of worrying about the lower level details. It’s the difference between thinking that “I want to bevel this edge” (high level), versus “I want to create the negative image of a bevel that I can subtract against an edge”.

Abstractions lets us think at a higher level, so we don’t have to think about the details. When we don’t have to think about all the details, we can keep more of the code in our heads when we’re working on it.

Let me know that you find these helpful by signing up for my mailing list to get more OpenSCAD guides as I write them. It’ll incentivise me to write more! See what I’ve written previously on OpenSCAD. If you’d like to see what else I work on, check out Cubehero, or follow me on twitter.

Tagged with: , , ,
Posted in openscad, tutorial

Spit shining Cubehero. Editing README, GFM, email notifications

Alright! So what parts of Cubehero got a spit shine over the Thanksgiving holiday? Here’s a quick fly-by update on what’s new.

Well for one, you can finally update the README.md file from the web site. Before, you had to create a text file called README.md and upload it in a commit. Now you can just edit the file in a textarea box now and click ‘update’, and Cubehero will submit a commit of the updated README.md to your project.

edit readme

And speaking of which the topic text, comments, and README.md now all support Github flavored markdown. Markdown is a writing format that converts to HTML. Github flavored markdown has some extensions that make it easier to write markdown.

github flavored markdown

When all of you post different discussions, I was often frustrated that I couldn’t keep updated on what was going on. So now, Cubehero sends email notifications for topics and comments that you post. If you own the physible, you will also get email notifications. It’s also easy to turn off the notifications. On the sidebar of the topic, you can turn on or off the notification.

email notifications

And lastly, each physible project now owns their own discussion section. Common discussion was too confusing, and didn’t fit the mental model of many users. There still will be a way to see discussion going on across all projects with the same name, but each project will have their own.

discussion for physibles

And that’s it! Enjoy. Lots of polishing before the next big push. As always let me know if you have any feedback or comments. Msg me on twitter, comment on this blog, or simply email me. wil@cubehero.com

Tagged with: , , ,
Posted in announcement

Emboss and impress images onto a surface in OpenSCAD

Earlier, I showed you how to extrude images in OpenSCAD, and a beginner’s guide to OpenSCAD. This time it’s a more advanced method on how to emboss images onto a surface in OpenSCAD, which requires some understanding of how to use the command line, and a little bit of Ruby.

Sometimes, half the fun of 3D printing is being able to take other formats and convert them into something printable. In this case, we want to be able to convert an image into a surface, a process called embossing.

lenna_embossing_example

Embossing is where we take an image and raise the surface on the lightest parts of the image. Impressing is the negative image of embossing, more like stamping an image onto a surface.

To do this, we’ll use the surface() command in OpenSCAD and a command line tool called Imagemagick. If you don’t currently have Imagemagick, go install it, depending on whether you’re on linux (ubuntu), mac (or using binaries), or windows.

Once you have that installed, we can use imagemagick from the command line to convert our image to grayscale.

We’ll use the classic sample image from the field of image processing, lenna.png.

lenna

To convert this to grayscale, all we have to do on the command line is:

convert lenna.png -type Grayscale lenna_result.png

lenna_grey

However, because we want to impress instead of emboss, we want to get the negative image instead. We’ll modify our existing command to:

convert lenna.png -type Grayscale -negate lenna_result.png

lenna_negated

Great! But the image is 512×512. We want to downsample for lower resolution, or else generating the resulting STL will take forever in OpenSCAD. We will modify our Imagemagick command line to resize it for us also:

convert lenna.png -type Grayscale -negate -resize 128×128 lenna_result.png

However, we still need to be able to read this data into OpenSCAD. We’ll need to use Imagemagick to convert to a raw binary format, where each pixel is a number representing its brightness. We’re going to use this number to represent height. Then once we have this raw binary format, we’ll use some Ruby code to read it in and write it out in a data file that OpenSCAD can read.

To export to a raw file format with an 8-bit grayscale depth, resized to 128×128 and negated, in summary, we do:

convert “lenna.png” -type Grayscale -negate -resize 128×128 -depth 8 gray:lenna.raw

Then we can read the raw image from ruby and write it to a date file with:

# raw2dat.rb
width = 128 # => width of resized raw image
str = File.binread('lenna.raw')
pixels = str.unpack("C*")

File.open('lenna.dat', 'w') do |f|
  pixels.each_with_index do |pixel, idx|
    f.write(pixel)
    if ((idx + 1) % width) == 0
      f.write("\n")
    else
      f.write(" ")
    end
  end
  f.write("\n")
end

All this is doing is reading the each byte as a height, and writing it as text, with line breaks. Once we have the data file, we can now read it in with OpenSCAD with the surface command:

// lenna.scad
mirror([0, 1, 0]) {
  scale([50 / 128, 50 / 128, 1 / 256])
    surface(file = "lenna.dat", convexity = 5);
}

lenna_scad

Because the coordinate system is different between screens and 3D models, we need to mirror the model. Then we scaled it to 50mm on each size, and the thickness to 1mm. The number 256 appears here, because we know we converted the image to an 8-bit grayscale, and hence, the brightness of the pixels range from 0 to 2^8 = 256. Hence, the maximum height in the raw file is 256.

I made it 1mm here, because then you can shine a light behind the plate, and the image will shine through, like the lamp this guy made. You may have to play around the thickness to get a desired brightness.

And that’s it! As a matter of course of testing out this idea, I ended up packaging up all of the above in a library called Embossanova. You can use it in one step like:

./embossanova impress lenna.png 128 128 1

Where `128 128` is the downsampled size, and `1` is the thickness of the resulting plate. More details on the README.md.

And that’s it! If you like the guide and want updates, join my OpenSCAD mailing list for updates, subscribe to this blog, or follow me on twitter.

Tagged with: , , , ,
Posted in openscad, tutorial, Uncategorized

Know only 10 things to be dangerous in OpenSCAD


OpenSCAD is a program used to make 3D models. But unlike most 3D modeling programs, there are only 10 things you need to know in order to be dangerous in OpenSCAD. Unlike most other 3D modeling programs like Blender, Sketchup, AutoCAD, or Solidworks, it’s really easy to get started in OpenSCAD.

Another difference is that you write a programming language to do your 3D modeling. “I’m not a programmer, you say!” Actually, OpenSCAD is a declarative language, like HTML. If you’ve ever written a simple blog post or email in HTML, you can handle OpenSCAD.

In addition, it’s a 3D modeling program based on constructive solid geometry (CSG), which means you’ll never make models with holes in the resulting 3D model mesh (however, you can still make bad models in another way). Holes in the 3D model makes it indigestible by slicing programs like skeinforge and slic3r, and hence, unprintable.

Lastly, unlike many 3D modeling or CAD programs, it’s entirely free! Not just free of charge, but it’s open source with a vibrant community.

So what are the 10 things you need to know? They are in 3 simple categories: shapes (cube, sphere, cylinder), transforms (translate, scale, rotate, mirror), and CSG operations (union, difference, intersection).

As a result, all you do in OpenSCAD is declare shapes, change them with transforms, and combine them with set operations. After we do a quick run down of the 10 things, we’ll combine them to make a bishop chess piece.

Shapes

There are only three basic 3D shapes you start with, and from these, you can make most any other shape. These are the cube, the sphere, and the cylinder.

1) Cube

The cube is pretty simple. You declare it with:

cube([10, 20, 15]);

cube

Don’t forget the semicolon! Now, there’s a cube with length 10, width 20, and height 15–each corresponding to each of the x, y, and z axis directions. In OpenSCAD if you refer something to be done along the x, y, and z directions, it will likely be in a vector, which is represented by numbers in brackets, like [x, y, z];

If you’d like a centered cube, that’s also pretty easy:

cube([10, 20, 15], center = true);

Easy, right?

2) Sphere

The sphere is pretty simple as well. You only have to declare the radius.

sphere(r = 20);

sphere

That gives you a sphere with a radius of 20. Notice that it’s centered at the origin already, unlike the cube.

Even easier.

3) Cylinder

The cylinder is a bit more complicated, but not by much.

cylinder(h = 40, r1 = 20, r2 = 20);

cylinder

This gives us a cylinder of height 10, and a radius of 20. Notice that unlike the cube, the parameters aren’t in a vector, because each of the numbers don’t correspond to the x, y, and z axis. Why is 20 repeated twice? It’s because there’s the radius of the top and bottom circles. If you don’t want to specify the radius twice, we can do this instead:

cylinder(h = 40, r = 20);

What if one of the radius was zero? Well, we’d get a cone instead by doing:

cylinder(h = 40, r1 = 20, r2 = 0);

Screen Shot 2013-11-19 at 3.10.54 AM

And like the cube, if you’d like to center it, all you have to do is add another parameter called ‘center’:

cylinder(h = 40, r = 20, center = true);

A bit harder, but still grade school stuff.

Transformations

Transformation is just a fancy way of saying moving and stretching something. The terminology is borrowed from linear algebra, which is the math behind most 3D modeling, so don’t be weirded out by it. There’s only four basic ways to transform a shape: translating (moving), mirroring (reflecting), scaling (stretching), and rotating.

4) Translate

Translation means moving an object by some amount along the x, y, and z axis. Remember that sphere that was centered? What if we wanted its south pole to be at the origin? Well, we’d translate (ie. move it up) along the z-axis by its radius. To do that, we do:

translate([0, 0, 20])
  sphere(r = 20);

shift up

Easy! And if we wanted to also move it along the x-axis in the opposite direction, we use negative numbers.

translate([-20, 0, 20])
  sphere(r = 20);

shift left

5) Scaling

Scaling is just shrinking or stretching a model along an axis. Taking our sphere again, say we want to make an ellipsoid.

scale([1.5, 1, 1])
  sphere(r = 20);

ellipsoid

That will make an ellipsoid that is 1.5 times the original size along the x-axis. So we get an ellipsoid that has a length of 30 (20 * 1.5), and a width and height of 20.

If we use a number between 0 and 1, we will shrink the model. This will shrink the y-axis of the ellipsoid by half:

scale([1.5, 0.5, 1])
  sphere(r = 20);

ellipsoid 2

Note that unlike translation, an unchanged axis in scaling is ’1′, not ’0′. If you put it ’0′, it will scale that axis by zero, which will result in no model, because 0 * anything = 0.

6) Rotation

Rotation can get tricky, but easy if handled in steps. Let’s start with a cube and rotate it 45 degrees counter-clockwise around the z-axis.

rotate([0, 0, 45])
  cube([10, 20, 15]);

rotate z

Like translation, if we want to rotate it in the opposite direction, we just use a negative number:

rotate([0, -30, 0])
  cube([10, 20, 15]);

rotate x

What happens if we rotate two axis, like?:

rotate([45, -30, 0])
  cube([10, 20, 15]);

45 then -30

The way to think about this is that we first rotate around the x-axis first by 45 degrees, and then we rotate the result by 30 degrees around the y-axis.

Unlike translation and scaling, the order you apply the rotations makes a difference. Rotating first around the x-axis then the y-axis is NOT the same as rotating first around the y-axis then the x-axis. Hence, a rotate statement will always apply the rotations in the order, around x-axis, around y-axis, then around z-axis.

So what if we want to apply the rotations in a different order? Well, first, note that the previous rotation can be written as:

rotate([0, -30, 0])  // then applied second
  rotate([45, 0, 0]) // applied first
    cube([10, 20, 15]);

A 45 degree rotation is applied first, before applying the 30 degree rotation. The inside-most rotate() wrapping cube() gets applied first, then the outside rotate() gets applied second. So if we want the rotation applied to the y-axis first, then the x-axis, we just switch the two rotate statements:

rotate([45, 0, 0])    // switched these
  rotate([0, -30, 0]) // two lines
    cube([10, 20, 15]);

-30 then 40

So far so good? This is the hardest of the four transformations. If you get this one, everything else is a breeze.

7) Mirroring

The last transformation is to reflect an object across the other side of a plane. Any plane is uniquely defined by vector perpendicular to it, called a normal vector. So to mirror across the yz-plane, the yz-plane is defined by the normal vector, [1, 0, 0].

First we’ll put a rotated cube, and then we’ll mirror a copy of across the yz-plane:

// rotated cube
rotate([0, 30, 0])
  cube([10, 20, 15]);
// mirrored across the yz-plane
mirror([1, 0, 0])
  rotate([0, 30, 0])
    cube([10, 20, 15]);

mirror

So if we have a normal vector of [1, 1, 0], the mirroring plane cuts at a 45 degree, as illustrated.

mirror([1, 1, 0])
  rotate([0, 30, 0])
    cube([10, 20, 15]);;

mirror 2

CSG Operations

Constructive solid geometry operations is just a fancy way of saying how we should combine shapes. If you remember sets from math class, that will probably help. But even if it doesn’t, the concepts are pretty simple.

8) Union

The simplest of the three set operations is union. Union is the sum of all shapes between its brackets.

union() {
  cylinder(h = 40, r = 10, center = true);
  rotate([90, 0, 0])
    cylinder (h = 40, r = 9, center = true);
}

union

9) Difference

Difference is using the second shape (and all subsequent shapes in the bracket) to cut out from the first shape.

difference() {
  cylinder(h = 40, r = 10, center = true);
  rotate([90, 0, 0])
    cylinder(40, r = 9, center = true);
}

difference

10) Intersection

Intersection is a little weird. It’s the overlapping part of all shapes between the brackets.

intersection() {
  cylinder(h = 40, r = 10, center = true);
  rotate([90, 0, 0])
    cylinder(40, r = 9, center = true);
}

intersection

Applying our new skills: bishop chess piece

Pretty easy so far, right? Now we’ll apply what we learned to make a bishop chess piece.

First, we want a teardrop shape of the bishop head. Let’s start with a sphere.

sphere(r = 20);

sphere

Then we add a cone to the sphere.

union() {
  sphere(r = 20);
  cylinder(h = 30, r1 = 20, r2 = 0);
}

cone and sphere

However, we want the radius of the cone to match up with the radius of the sphere at a given height we move up the cone. We can use sin and cos to figure that out.

union() {
  sphere(r = 20);
  translate([0, 0, 20 * sin(30)])
    cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);
}

teardrop

Now we have our teardrop shape, let’s cut the slot in the bishop’s head. First we have to make the slot as a rectangle, and cut it out.

difference() {
  union() {
    sphere(r = 20);
    translate([0, 0, 20 * sin(30)])
      cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);
  }
  cube([40, 5, 40]);
}

add slot

But the slot isn’t in the right place, so we’ll have to center it first.

difference() {
  union() {
    sphere(r = 20);
    translate([0, 0, 20 * sin(30)])
      cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);
  }
  translate([-20, 0, 0])
    cube([40, 5, 40]);
}

centered slot

And then we rotate it by 45 degrees.

difference() {
  union() {
    sphere(r = 20);
    translate([0, 0, 20 * sin(30)])
      cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);
  }
  rotate([45, 0, 0])
    translate([-20, 0, 0])
      cube([40, 5, 40]);
}

Screen Shot 2013-11-19 at 1.46.01 AM

Now, let’s add a dollop on top. Since we know the height of the cone is 30, and we moved it up 20 * sin(30), we’ll need to translate the dollop 30 + 20 * sin(30). We’ll also comment the parts so we don’t get confused.

difference() {
  union() {
    // teardrop shape
    sphere(r = 20);
    translate([0, 0, 20 * sin(30)])
      cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);

    // dollop
    translate([0, 0, 30 + 20 * sin(30)])
      sphere(r = 6);
  }
  //cut out slot
  rotate([45, 0, 0])
    translate([-20, 0, 0])
      cube([40, 5, 40]);
}

add dollop

We need the bishop to have a neck and a base. Let’s keep it simple and use cylinders. We’ll lift the head up, and put a neck and base underneath.

union() {
  // head
  translate([0, 0, 120])
    difference() {
      union() {
        // teardrop shape
        sphere(r = 20);
        translate([0, 0, 20 * sin(30)])
          cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);

        // dollop
        translate([0, 0, 30 + 20 * sin(30)])
          sphere(r = 6);
      }
      //cut out slot
      rotate([45, 0, 0])
        translate([-20, 0, 0])
          cube([40, 5, 40]);
    }
  // neck
  cylinder(h = 120, r1 = 18, r2 = 12);

  // base
  cylinder(h = 20, r1 = 35, r2 = 25);
}

add neck and base

It looks a little naked. Lastly, let’s put a collar on the bishop. To make a collar, we’ll intersect a cone with an inverted version of itself. We first start with a cone with its mirror.

cylinder(h = 20, r1 = 20, r2 = 0);
mirror([0, 0, 1])
  cylinder(h = 20, r1 = 20, r2 = 0);

Screen Shot 2013-11-19 at 2.05.50 AM

Then we’ll take the mirrored version and shift it up, then take the intersection.

intersection() {
  cylinder(h = 20, r1 = 20, r2 = 0);
  translate([0, 0, 7])
    mirror([0, 0, 1])
      cylinder(h = 20, r1 = 20, r2 = 0);
}

intersection of two cones for a collar

Putting it together with the rest of it, we get:

union() {
  // head
  translate([0, 0, 120])
    difference() {
      union() {
        // teardrop shape
        sphere(r = 20);
        translate([0, 0, 20 * sin(30)])
          cylinder(h = 30, r1 = 20 * cos(30), r2 = 0);

        // dollop
        translate([0, 0, 30 + 20 * sin(30)])
          sphere(r = 6);
      }
      //cut out slot
      rotate([45, 0, 0])
        translate([-20, 0, 0])
          cube([40, 5, 40]);
    }
  // neck
  cylinder(h = 120, r1 = 18, r2 = 12);

  // base
  cylinder(h = 20, r1 = 35, r2 = 25);

  // collar
  translate([0, 0, 90])
    intersection() {
      cylinder(h = 20, r1 = 20, r2 = 0);
      translate([0, 0, 7])
        mirror([0, 0, 1])
          cylinder(h = 20, r1 = 20, r2 = 0);
    }
}

full bishop

And that’s it! That’s really all you need to know to get started and get dangerous in OpenSCAD. If you would like to see how other chess pieces were written, check out king’s gambit hosted here at Cubehero, my first 3D printed project using OpenSCAD. If you’d like to learn more, check out a previous more advance tutorial on how to generate patterns from images with OpenSCAD.

Let me know if you find these helpful. And if you’d like to hear of more in the future, signup for my mailing list to get more OpenSCAD guides as I write them. Or just follow me on twitter.

Update: For those of you curious about how the tangent cone’s dimensions were calculated:

tangent cone

 

Tagged with: , , ,
Posted in openscad, technical, tutorial

How to generate extruded 3D model from images in OpenSCAD

Recently, to do a little bit of 3D modeling, I wanted to try my hand at making a modular back cover for my iPhone. Yes, there’s too many 3D printable models of iPhone covers, but sometimes, we reinvent wheels not to have more wheels, but to have more inventors. To do something a bit more organic, my concept was to have a pattern of leaf veins for my iPhone backcover, Graftleaf.

However, instead of drawing a leaf pattern myself, I generated the pattern from an image using free tools. That’s what the rest of this tutorial will help you do, and what I learned.

First, I had to go find a creative commons image of some leaf veins. I need something that was CC-BY, which allowed derivative works. I also needed the image to be sharp (damn you, tilt-shift) with high contrast. I used flickr’s advanced search, so you can look for CC images.

There were a good number of results, but I picked this one by Steve Jurvetson, which fulfilled my criteria above.

Next, I fired up GIMP, an open source alternative to Photoshop. After you open up GIMP, then open up the leaf vein picture above through File -> Open.

Screen Shot 2013-11-09 at 6.34.06 PM

Now we want to convert it to grayscale. Image -> Mode -> Grayscale.

Screen Shot 2013-11-09 at 6.34.50 PM

However, the colors are inverted. We want the veins to be black. Colors -> Level.

Screen Shot 2013-11-09 at 6.35.41 PM

Move switch the positions of the black and white arrows in “Output Levels”

Screen Shot 2013-11-09 at 6.36.28 PM

Screen Shot 2013-11-09 at 6.37.36 PM

Now, we want to make the veins a solid black and white. Choose Colors -> Threshold. Adjust it until it the veins are thick, but there aren’t too many spots in the cells between the veins.

Screen Shot 2013-11-09 at 6.38.03 PM

Screen Shot 2013-11-09 at 6.39.27 PM

Lastly, we want to crop the image to make the details more visible, as well as reducing the complexity of the image, so the PNG to DXF conversion won’t take a long time.

Screen Shot 2013-11-09 at 6.44.45 PM

Screen Shot 2013-11-09 at 6.44.57 PM

Now that our image is edge detected, and we can now save it to a file as a PNG. Now, we’ll use an open source tool for vector drawings, called Inkscape, to convert the PNG to DXF.

This is where OpenSCAD gets a little finicky. It can’t read all types of DXF files, so we need to install an Inkscape extension that allows us to save DXF files in a format OpenSCAD can read. Go download the extension. To install it in Inkscape, copy the *.py and *.inx files (in the zip file of the extension) to your Inkscape extensions folder. Where the extensions folder is located depends on what system you’re using.

  • Linux: ~/.config/inkscape/extensions/
  • Mac OS X: /Applications/Inkscape.app/Contents/Resources/extensions/
  • Windows: C:\Program Files\Inkscape\share\extensions\

(If you’re all-pro, you can use git to clone the extension into the inkscape extension folder.)

Once you have that installed, open up Inkscape and load up the leaf PNG we just saved. When it asks you if you want to link or embed, choose embed.

Screen Shot 2013-11-09 at 7.19.03 PM

Next, we want to trace the bitmap we have as a path. First select the bitmap by clicking on it. Then Path -> Trace Bitmap. I just used the default options, but you can play around with things then click update to see what it would look like.

Screen Shot 2013-11-09 at 7.19.47 PM

Screen Shot 2013-11-09 at 7.26.38 PM

Now you have two objects on the canvas. One is a vector path of the leaf veins, overlaid on top of the bitmap of the leaf veins. Move the vector path out of the way by dragging and dropping it. Then select the bitmap and delete it. Then move the vector path back.

Screen Shot 2013-11-09 at 7.28.26 PM

Now, we can save it to DWF using the extension we installed earlier. File -> Save As. Make sure you select “OpenSCAD DXF Output (*.DXF)” as the file type to save as.

Screen Shot 2013-11-10 at 10.17.09 AM

We finally have our DXF file that we can use in OpenSCAD, as you can see all the assets.

Now, we can use it in our OpenSCAD! We can write the OpenSCAD snippet:

linear_extrude(height = 4)
import(“leaf_outline.dxf”);

Screen Shot 2013-11-10 at 10.45.43 AM

In making Graftleaf, I actually did the additional step of compiling with CGAL (F6 in OpenSCAD), and then exporting the result to STL. Then using the STL in the final model, for faster renders.

Screen Shot 2013-11-10 at 10.47.36 AM

a156ce41c3d24cb5bba37f5a6357afd7388bd77f_default

If you found this more advanced OpenSCAD tutorial helpful, let me know! I’ll write more. Have a great week!

Tagged with: ,
Posted in openscad, tutorial

Work on 3D printed projects together.
Host your 3D printed projects on Cubehero, and get 3D Model previews and git based version control.
https://cubehero.com

Follow

Get every new post delivered to your Inbox.

Join 263 other followers