Showing posts with label graph. Show all posts
Showing posts with label graph. Show all posts

Wednesday, March 30, 2016

Joining two circles/cylinders in openscad 2: Code and Pretty pictures

In an previous we developed the equations to make a nice curve to join two circles (or cylinders). Note that can be used anywhere, including in 3D drawing. I will cheat by using a 3D modelling program that allows me to enter the equation in some kind of programmatic way, it is open source, and can be run in the usual operating systems. If you read the title of this article, you probably figured out by now we are going to use OpenSCAD. And you would be right.

Openscad differs from most CAD programs in that you do not draw interactively using a GUI. Instead you describe the objects using a programming language. Let me show what I mean using a really basic example which I stole from the manual:

module test1()
{
        cube([2,3,4]);
}

test1();

which will draw a cube of sides 2, 3, and 4 (new definition of the word "cube" we were not aware of; just roll with it). The files have a .scad extension by default, so I named this test file test1.scad. The main "entity" is a module. Inside a module you do your 2D or 3D drawings. You can have modules inside modules and modules calling other modules that are somewhere else. And then you need to finally call the module that calls everything else so something is drawn (we call it rendered). We can also define functions and variables (most of which behave like constants). Instead of keeping making new examples, which the online manual already has plenty, let's say we want to design some kind of hinge thingie that looks like this (gaze upon the magnificence of this engineering sketch):

We will develop the file that describe such 3D object as we go.

Since we are making a hinge, I will call the file hinge.scad. This is how a completely barebones version of the file looks like:

module hinge()
{
}

hinge();

Not much going on, but at least it will run. I will assume all my units are in mm. And I will say the two holes are 10mm and 15mm in diameter. And the hinge thickness is 2mm, or 20% of the smaller circle's diameter. Putting all of that in our little file, we have

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/
module hinge()
{
   // Known quantities
   hole1_dia = 10;
   hole2_dia = 15;
   hinge_thickness = hole1_dia*.2;
}

hinge();

The hole name comes from we are deciding how big the hole for the pins/bolts that will hold the hinge to the frame and door need to be. I am throwing some numbers here, but in real life those are known quantities (1: grab bolt. 2: measure its diameter). The hinge thickness is how much meat will be around those bolts so the hinge will not collapse like a bunch of broccoli. Of course the minimum thickness is material dependent.

With that said, here are some interesting things to note in the file so far:

  1. Comments are very similar to C/C++
  2. Statements end with a ;
  3. hinge_thickness may seem to be a variable but it is evaluated when the file is compiled, not when it is run.

How does the file look so far in openscad? Let's load it and see:

Note we have not drawn anything yet, so there is nothing to be seen. How about if we make those two circles? Because of the simple shape of the hinge, we can do the hard work in 2D and then extrude it. This is also a good time to decide the distance between the two holes; I'll say their centers are 32mm apart, which means the circles are 30 -7 -5 = 18mm apart, and that distance henceforth shall be named circle1_height. Let's update the code and tell it to dry the two circles:

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/        
module hinge()                                                                  {                                                                                  
   // Known quantities                                                             
   hole1_dia = 10;
   hole2_dia = 15;
   hinge_thickness = hole1_dia*.2;
   circle1_height = 30;

   module body()
   {
      // circle2 will be located at the origin
      circle( r=(hole2_dia + 2*hinge_thickness)/2 );                              
      // circle1 will be located at y = circle1_height from circle2
      translate([0, circle1_height, 0])                                               
      circle( r=(hole1_dia + 2*hinge_thickness)/2 );
   }

   body();
}

hinge();

So, the body starts with the two circles (we have not drilled the holes for the bolts yet), where circle2 is drawn on the center of the coordinate system (coordinate [0,0,0]) and circle1 is drawn by first moving to coordinate [0,circle1_height,0] (moving up on Y axis of circle1_height and then drawing it. It does feel like you are telling it to move an imaginary 3D plotter head around to do your drawings.

And here is how it looks like on the screen. Openscad is representing the 2D objects in 3D by giving them a thickness of one unit.

Making progress; now we need to connect the two to get the general shape of the hinge.

The Lazy Way

Do you remember the K.I.S.S. (Keep It Simple, St.. er Oh Brave Enterprising One)? It is really a variation of being lazy: what is the least amount of work you have to do to get the job done? For our case, what would be the simplest way to connect the two circles? Two points define a line, so if we use a point on each circle we can draw a line between them. Mirror that and we have enclosed the mess. We will need an easy point on each circle, I do not want to think too hard so here is my proposal: if we draw a line connecting the centers of the two circles, we can then draw the diameters perpendicular to this line. Where these two lines intersect each circle define two points as shown in the picture on the right, which we stole from a previous article.

The way we will create the region connecting the two circles is by creating a trapezoid and then joining it to the circles. This will make more sense in when you see it. So, let's modify the code and introduce a command called polygon().

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/        
module hinge()                                                                  {                                                                                  
   // Known quantities                                                             
   hole1_dia = 10;
   hole2_dia = 15;
   hinge_thickness = hole1_dia*.2;
   circle1_height = 30;

   module body()
   {
      // Create the trapezoid
      module fillerStuff()
      {
         polygon(points=[[-(hole2_dia + 2*hinge_thickness)/2,0],[-(hole1_dia + 2*hinge_thickness)/2,circle1_height], [(hole1_dia + 2*hinge_thickness)/2,circle1_height], [(hole2_dia + 2*hinge_thickness)/2,0]], paths=[[0,1,2,3]]);
      }
 
     // circle2 will be located at the origin
      circle( r=(hole2_dia + 2*hinge_thickness)/2 );                              
      // circle1 will be located at y = circle1_height from circle2
      translate([0, circle1_height, 0])                                               
      circle( r=(hole1_dia + 2*hinge_thickness)/2 );

      // Draw the trapezoid
      fillerStuff();
   }

   body();
}

hinge();

Let's see how it looks like:

Maybe a top view?

It is hard to notice from these pretty pictures, but one issue our code is that it is drawing 3 different objects on the screen instead of one. If all you want to do is draw something on the screen, this might sound pedantic. After all, it looks good so far. But we are approaching this 3D drawing business from a programming standpoint. And it is good programming practice to use objects made of smaller objects as needed. As in Java (gasp!) C++ (Ugh!) and Python (Irk!), once the object is defined, we can manipulate this single object. For instance, we can apply transformations -- resize, distort, move, etc -- to one single object instead of each of its components. And, OpenSCAD is smart enough not to draw the areas common to the parts that make this object. OpenSCAD calls a module() what we would call an object; I think by now you have noticed the module() is the main entity in this program.

Enough of this boring talk, let's see how we do it. So we need to join them using union(). And while we are at it, let's show the 3 different objects that make this union by adding a #, the debug modifier to the command used to draw the trapezoid:

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/        
module hinge()                                                                  {                                                                                  
   // Known quantities                                                             
   hole1_dia = 10;
   hole2_dia = 15;
   hinge_thickness = hole1_dia*.2;
   circle1_height = 30;

   module body()
   {
      // Create the trapezoid
      module fillerStuff()
      {
         polygon(points=[[-(hole2_dia + 2*hinge_thickness)/2,0],[-(hole1_dia + 2*hinge_thickness)/2,circle1_height], [(hole1_dia + 2*hinge_thickness)/2,circle1_height], [(hole2_dia + 2*hinge_thickness)/2,0]], paths=[[0,1,2,3]]);
      }
 
      union()
      {
         // circle2 will be located at the origin
         circle( r=(hole2_dia + 2*hinge_thickness)/2 );

         // circle1 will be located at y = circle1_height from circle2
         translate([0, circle1_height, 0])
         circle( r=(hole1_dia + 2*hinge_thickness)/2 );

         // Draw the trapezoid
         # fillerStuff();
      }
   }

   body();
}

hinge();

And here is the output. The view is still the same, but now we can clearly see the fillerStuff() module compared to the two circles.

Looks like everything is nice and rosy. Or rosy and yellow.

Or, are they?

Tangents anyone?

Our lazy way would have worked perfectly if both circles had the same diameter. But they are not. I know the last few pictures look very nice, but let me see if I can show you the issue by changing circle1_height to, say, 20:

You might have to click on the picture to see it, but the intersection between the line and the small circle looks very wrong because they are at different angles.

Er, it doesn't. I think we will need to crank up the resolution a bit by setting $fn=100.

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/
module hinge()
{
   // Known quantities
   hole1_dia = 10;
   hole2_dia = 15;
   hinge_thickness = hole1_dia*.2;
   circle1_height = 15; // Y-axis
   $fn=100; // crank up the resolution
[...]

That will make rendering slow but should smooth things out. I also made circle1_height even smaller (15), to the point the two circles touch themselves (bonus point: why do they are more than just touching each other?) and make where the circles touch the line even more dramatic:

That will only look nice if the line is tangent to the circle. Since said line touches both circles, it must be tangent to both of them. What we are looking for is the external common tangent, whose explanation and methods of obtaining are described in many locations; I used this one. Let's modify the code to calculate the points in the two circles that satisfy that and then draw the trapezoid around them; I will try later on to talk about how to obtain it as applied to OpenSCAD.

For now we need to know how to do asin(), functions.

Here is how the code looks like. I modified it a bit so the 4 points used to make the trapezoid are calculated before it is drawn.

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/        
module hinge()                                                                  {                                                                                  
   hole1_dia = 10; // Top circle
   hole2_dia = 15; // Bottom circle
   hinge_thickness = hole1_dia*.2;
   circle1_height = 15; // Y-axis
   $fn=100; // crank up the resolution

   // Useful functions
   function geta1(r1, r2, k) = k*r2/(r2 - r1);
   function getAlpha(opposite_side = 1, hypothenuse = 1) =
                     asin(opposite_side/hypothenuse);

   module body()
   {
      // Create the trapezoid
      module fillerStuff()
      {
         /************** Lazy
         point0 = [-(hole2_dia + 2*hinge_thickness)/2, 0];
         point1 = [-(hole1_dia + 2*hinge_thickness)/2, circle1_height];
         point2 = [(hole1_dia + 2*hinge_thickness)/2, circle1_height];
         point3 = [(hole2_dia + 2*hinge_thickness)/2,0];
          *************/

         r1 = (hole1_dia + 2*hinge_thickness)/2;
         r2 = (hole2_dia + 2*hinge_thickness)/2;
         a1 = geta1(r1, r2, circle1_height);
         alpha = getAlpha(r1, a1);
         echo("Opposite side for small triangle: ", a1);
         echo("Angle in degrees: ", alpha);
         y1 = circle1_height + r1*sin(alpha);
         y2 = r2*sin(alpha);
         x1 = r1*cos(alpha);
         x2 = r2*cos(alpha);
         echo(x1=x1, y1=y1, x2=x2, y2=y2);

         // Points that make the trapezoid
         point0 = [-x2, y2];
         point1 = [-x1, y1];
         point2 = [x1, y1];
         point3 = [x2, y2];

         polygon(points=[point0, point1, point2, point3], paths=[[0,1,2,3]]);
      }
 
      union()
      {
         // circle2 will be located at the origin
         circle( r=(hole2_dia + 2*hinge_thickness)/2 );

         // circle1 will be located at y = circle1_height from circle2
         translate([0, circle1_height, 0])
         circle( r=(hole1_dia + 2*hinge_thickness)/2 );

         // Draw the trapezoid
         # fillerStuff();
      }
   }

   body();
}

hinge();

We are also using the echo function to print out to the console some intermediate values. I used that to debug my code, which was wrong on the first trial and forced me to redraw the image so I could get the right equations. Here is how this output looks like:

Compiling design (CSG Tree generation)...
ECHO: "Opposite side for small triangle: ", 57
ECHO: "Angle in degrees: ", 7.05413
ECHO: x1 = 6.94701, y1 = 15.8596, x2 = 9.42809, y2 = 1.16667
Compiling design (CSG Products generation)...
Geometries in cache: 44
Geometry cache size in bytes: 154304
CGAL Polyhedrons in cache: 0
CGAL cache size in bytes: 0
Compiling design (CSG Products normalization)...
Compiling highlights (1 CSG Trees)...
Normalized CSG tree has 2 elements
Compile and preview finished.
Total rendering time: 0 hours, 0 minutes, 0 seconds

The ECHO statements are the output of the echo commands we inserted in the code. They are only printed to the console window. So, did our extra coding effort paid off? Here is the image with the new trapezoid nodes.

Note the transition between circles and straight lines look much nicer.

A Touch of Flair

I think we are making a lot of progress: we have a hinge that looks very smooth. But, let's not rest on our laurels just yet. I think we can do it even nicer, and get really close to the first picture in this article. And we shall do so by modifying our code to use the equations we created in a previous 3D drawing article. Note that once again we commented out the lines for the previous joining solution, tangents, so they are in the same code.

/**********************************************************************
 * Let's make a stylish door hinge, shall we?
 *
 * NOTEs:
 * o Units are in mm
 **********************************************************************/        
module hinge()                                                                  {                                                                                  
   hole1_dia = 10; // Top circle
   hole2_dia = 15; // Bottom circle
   hinge_thickness = hole1_dia*.2;
   circle1_height = 15; // Y-axis
   $fn=100; // crank up the resolution
   cutout_dia = 50;

   // Useful functions
   function geta1(r1, r2, k) = k*r2/(r2 - r1);
   function getAlpha(opposite_side = 1, hypothenuse = 1) =
                     asin(opposite_side/hypothenuse);
   // Fancy curves
   function getC2(a=1, b=1, c=1) = (pow(a,2) + pow(c,2) - pow(b,2))/(2*c);
   function getCx0(hip=1, side=1) = sqrt(pow(hip,2) - pow(side,2));

   module body()
   {
      // Create the trapezoid
      module fillerStuff()
      {
         /************** Lazy
         point0 = [-(hole2_dia + 2*hinge_thickness)/2, 0];
         point1 = [-(hole1_dia + 2*hinge_thickness)/2, circle1_height];
         point2 = [(hole1_dia + 2*hinge_thickness)/2, circle1_height];
         point3 = [(hole2_dia + 2*hinge_thickness)/2,0];
          *************/
         r1 = (hole1_dia + 2*hinge_thickness)/2;
         r2 = (hole2_dia + 2*hinge_thickness)/2;
         /************** Tangent
         a1 = geta1(r1, r2, circle1_height);
         alpha = getAlpha(r1, a1);
         echo("Opposite side for small triangle: ", a1);
         echo("Angle in degrees: ", alpha);
         y1 = circle1_height + r1*sin(alpha);
         y2 = r2*sin(alpha);
         x1 = r1*cos(alpha);
         x2 = r2*cos(alpha);
         echo(x1=x1, y1=y1, x2=x2, y2=y2);
          *************/

         a = (2*r2 + cutout_dia)/2;
         b = (2*r1 + cutout_dia)/2;
         c = circle1_height;
         c2 = getC2(a, b, c);  // Projection of a onto c (y axis)
         c1 = c - c2;   // Projection of b onto c (y axis)
         Cx0 = getCx0(a, c2);
         C0 = [Cx0, c2]; // Tangent circle center
         C1 = [-Cx0, c2]; // Tangent circle center
         // Tangent points as coordinates
         x2 = Cx0*r2/a;
         y2 = c2*r2/a;
         x1 = Cx0*r1/b;
         y1 = c - c1*r1/b;

         // Points that make the trapezoid
         point0 = [-x2, y2];
         point1 = [-x1, y1];
         point2 = [x1, y1];
         point3 = [x2, y2];

         // Draw it
         difference()
         {
            polygon(points=[point0, point1, point2, point3], paths=[[0,1,2,3]]);
            translate(C0)circle(r=cutout_dia/2, center=true);
            translate(C1)circle(r=cutout_dia/2, center=true);
         }
      }
 
      union()
      {
         // circle2 will be located at the origin
         circle( r=(hole2_dia + 2*hinge_thickness)/2 );

         // circle1 will be located at y = circle1_height from circle2
         translate([0, circle1_height, 0])
         circle( r=(hole1_dia + 2*hinge_thickness)/2 );

         // Draw the trapezoid
         # fillerStuff();
      }
   }

   body();
}

hinge();

Fun fact: If you are using a PLA filament 3D printer, having the outside sides of the trapezoid be curved will look nicer than going from circle to straight line. I was told it is because the printer needs to stop when going from curve to straight. But we can talk about printing our handiwork in another article.

You may have also noticed how progressively more complex the code has become. But that is a small price to pay for beauty. And now let's see what all that effort brings us

Not bad. What if we increase circle1_height to, say, 20?

Looks very smooth. Now we then take the # from

// Draw the trapezoid
         # fillerStuff();

Er, what about the holes for the pins/bolts?

Good catch! I forgot completely about them! So, how about if we change

body();

to

module body_hole()
   {
      union()
      {
         // circle2 will be located at the origin
         circle( r=hole2_dia/2 );

         // circle1 will be located at y = circle1_height from circle2
         translate([0, circle1_height, 0])
         circle( r=hole1_dia/2 );
      }
   }

   difference()
   {
      body();
      body_hole();
   }

Let's see how it looks like

A nice angled view perhaps?

And, finally, let's give some thickness to this hinge by using the linear_extrude() module (I am cheating and saying the hinge is circle1_height thick in the code excerpt below).

module body_hole()
   {
      union()
      {
         // circle2 will be located at the origin
         circle( r=hole2_dia/2 );

         // circle1 will be located at y = circle1_height from circle2
         translate([0, circle1_height, 0])
         circle( r=hole1_dia/2 );
      }
   }

   linear_extrude(height = circle1_height, center = false, convexity = 10)
   difference()
   {
      body();
      body_hole();
   }

And look at it

I think it looks very nice, how about you? If you agree with me, I think we succeeded into doing a quick intro to using OpenSCAD. Ok, it is neither quick nor really intro, for I rushed a bit in some of the equations. The ones for the fancy hinge were derived in a previous article, but that is not the case for the tangential solution. How about this: I will make later on a new article showing how those equations were derived. And then, we will do some 3D printing to see how our handiwork looks! Sounds like a plan?

Tuesday, December 22, 2015

Joining two circles/cylinders in openscad 1: Geometry

Warning!

This article has more to do with geometry than IT in general. You might possibly be scared of it, and I think that repulse to math in general and geometry specifically is a valid reaction. and I would suggest you to skip to the next posting. Or you might want to go on if just for the pretty pictures. That suits me fine also; you are the one with decider here.

You have been warned!

Recently I decided to learn how to use openscad. As the name kinda implies, it is a (open source) program to build 3D solids. Main difference with the usual suspects is that it is not interactive: you create a file using your favourite text editor (or sed for that matter) and then compile it. That might sound a bit annoying but it can be quite convenient if you are doing engineering objects as you can define the different object that make it as separate functions, which can even become libraries. However, it does not have some features you expect to find in such programs, like fileting. They are still working on providing a official solution to that, but as of right now it ain't there.

Now, sometimes you can go around these limitations by using a bit of geometry. Let me show what I mean by using a simple example: Let's say I want to build a bracket that will hold two pipes together (think chainlink fence gate) that looks like this sketch:

For now we will ignore the pipe holes and look at the external shape of the object. Since we are going to have two circles internally, it would be nice if the ends had a circly feel to it. So, we shall start with two circles around the pipe holes. To make it look pretty, and easy when we do get to build it, how about if the pipe holes are 90% in diameter of those circles we are using to make the body? You know, something like the picture (done in inkscape without making any justice to its potential) on the right.

So we have the ends of the body, but what about the sides? We need to connect the two ends somehow.

The easiest thing I can think of is to use those lines representing the radius of each circle, which happen to be both vertical. We could just connect them on each side (showing just the upper side to keep figure clean and since the object will be symmetric with respect to the horizontal, henceforth called X, axis).

I don't know about you, but even though it might work around the larger (right) circle, it would look odd on the smaller (left) circle: the surface would become parallel to the X axis just before going vertical and then it would make a sharp turn and start moving away of this axis. It just looks like it was put together in a hurry, and I think we can do better.

And how do you plan on doing that? you might ask. Or not. But this is my article so I will answer that question. Our goal is to have the side to transition gently away from the circle. I think we are looking for some kind of curve that started tangent to one circle and then, without much ado, end tangentially to the other circle. Kinda like the picture on the left, with some simple gentle curve in the middle. That does look pretty, but it requires us to determine where it will move away from each circle. In other words, we might need to do some fiddling.

I am lazy, so I would like to keep any fiddling to a minimal. Therefore, I want a solution that makes more work for the computer and less to me. I therefore need a curve that I will provide as few parameters as possible and it will still end up nice and pretty. Euclidean Geometry has the answer: the easiest way to come up with a curved surface that touches both circles tangentially mathematically with the minimal amount of input is to use a third circle.

Here is what I have in mind in a drawing (most of the green circle has been omitted so we would not focus on it): the green circle touches each of the other two circles tangentially as we want. I know the curve in the drawing does not look as nice as the one in the previous drawing, but where it touches and the shape of the curve it creates between those points is determined solely by its radius. Specifically, the bigger the green circle is the closer the line between the blue circles will be to a straight line. The other required information is determined by the two pipes we mentioned before, namely:

  1. Radius of the two blue circles
  2. Distance between the two blue circles.

So far so good, thanks to geometry. Let's see if we can continue relying on it and come up with equations that will make our life lazier. First thing we will need is some lines connecting the centers of the three circles (as I mentioned before, I am ignoring the other side due to the symmetry the object has) together. As to be expected, that defines a triangle of some arbitrary (we did not specify any relationship between the radius of the 3 different circles. We will revisit that later) shape, which in our little drawing is represented by the black triangle.

Before we continue, we must do some labeling. So, the centers of the 3 circles are called A, B, and C, which as we know are the vertexes of the triangle ABC. The sides of the triangle are labelled a, b, and c where b is opposite to B and so on. We can also name each circle after its center, so circle C is the green circle. Distance between C and side c is h.

From the picture we can also figure out the radius of each circle is being named and that a is tangent to circles B and C (and b is tangent to circles A and C). Finally, h divides c in the segments c1 and c2 and creates two right triangles. With those parameters defined, we need to start defining a few equations.

First we restate the relationship between the sides a, b, and c and known quantities:

Well, we really do not know c1 or c2, but can find them by applying Pythagoras on the left triangle:

Since we have c2, we can find c1. So, now we can locate the circle C. Next time we will put all these findings in openscad and then do some doodling.