2021-06-09
md
First Designs with OpenSCAD
<-First 3D Prints with an ANYCUBIC Mega Zero

After printing a few objects created by others with my new 3D printer (see First 3D Prints with an ANYCUBIC Mega Zero), I wanted to try my hand at creating a practical 3D object. Two three-dimensional A's with curly hooks at the end seemed like a reasonable tablet stand. The design looked simple enough since it’s basically just three slabs of plastic arranged in a triangle. The objects were relatively small so it would not take too long to print, which is not negligible when starting out printing 3D objects and anticipating many problems. The generated image on the right shows what I came up with using OpenSCAD. I felt at ease with that particular open source CAD program which can be described as the conflation of high school geometry and a C-like scripting language.

Creating 3D models resembles writing programs. Something that is "fit for purpose" must be attained, but just what that means is rather nebulous. Furthermore, and no matter how that goal is defined, there are many, many ways to achieve it. Invariably, there is no single, obviously correct, way of proceeding. From long experience, I know that it will always be possible to improve any program that I write. I also know that it is possible to really mess up when "improving" a program... long live version control. It looks this will be the case for my 3D models.

Table of Contents

  1. Solid Geometry
  2. Freehand Sketch
  3. First J-Hook
  4. First Support
  5. Second J-Hook
  6. By Hook or by Crook
  7. Take Two
  8. Future Version
  9. Downloads

Solid Geometry toc

Objects in OpenSCAD are created from elementary solid geometric shapes:

The geometric shapes are joined in an additive process to build up a 3D object. It is also possible to subtract a shape from another. The basic geometric shapes, or any combination of their union or their difference no matter how complex, can be scaled, translated and rotated as in vector drawing programs such as Inkscape. All these operations are implemented as functions (union, difference, scale, translate and rotate). A 3D model is a script describing the object which is then "compiled" to render the object. It is possible to export a 3D model built with OpenSCAD to many different formats including STL which "slicer" programs such as Ultimaker Cura can transform in G-Code which can be viewed as the machine language of 3D printers.

Let's look at a simple illustrative example. The hard drives in an old computer were mounted with custom-made screws that had a head which slid in slots. It was an elegant solution, but unfortunately no such screws were on hand when it was time to add another drive. I decided to 3D print bushings to fit in the slot. The photograph shows one of these on the left.

The bushing was very easy to make because the piece is made of two cylinders. By default cylinders are drawn with their principal axis measured along the z coordinate. Consequently the taller cylinder is exactly centred within the wider one. The $fn at the start of the script is a special variable that controls the number of fragments used to render a circle which in our case is the number of rectangular facets used in making each cylinder. The image on the left is a screen capture of the first step in creating a 3D model of the bushing in OpenSCAD. On the left is the editor with the complete script and the preview window is on the right. A hole has to be punched into the centre. That is just another cylinder that must be removed from the previous two cylinders.

The boolean combination difference has the following syntax:

     difference { object, object [, object] }

It subtracts from the first object the second one and all subsequent objects. Since we wanted to subtract the third cylinder from the first two, it was necessary to group the first two with the union boolean operator.

Here is another, albeit contrived, example that uses other elements of the OpenSCAD language.

These two images were built with a cube and six cylinders. The script below is the source code for building these two shapes. To render the building block shape on the left set the variable bblock to true, to get the stretched brick shape on the right set bblock to false.

$fn = 64; printBblock = false; if (printBblock) { cube([30, 20, 10]); pillars(2); } else { scale([1.5, 1, 1]) difference() { cube([30, 20, 10]); pillars(0); } } module pillars(zval) { assert(zval >= 0, "zval cannot be a negative translation"); for (i = [0:2]) { for (j = [0:1]) { translate([5+i*10, 5+j*10, zval]) cylinder(h=10, d=5); } } }

The script shows that OpenSCAD is really a full-pledged programming language. Note how the six cylinders are built in a module which makes for easy reuse of code. Just like Pascal procedures or a C functions, modules take parameters which can modify their behaviour. In our case, the zval specifies a common translation in the z-direction of the cylinders. I included an assertion to validate the value of the zval parameter just for show as there is no reason to prohibit negative translations of the cylinders. The module also shows that for loops are available. The main part of the code is a logical if else statement. C syntax is used here, meaning that the outer brackets around the logical expression being evaluated are mandatory.

This is by no means an introduction to the OpenSCAD scripting language. The software is mature with good documentation including a detailed User Manual and complete Language Reference. Most will probably want to at least look at the very goodTutorial before delving into the manual or language reference.

What follows explores only a very small fraction of what the program can do. Remember I am just starting to play with 3D printing and CAD modelling. This is not quite a case of the "blind leading the blind," but this post is very much in the spirit of this site where writing about newly learned material is a good way to consolidate recently acquired knowledge.

Freehand Sketch toc

Re-inventing the wheel is not something easy to do. However once the principle is understood, it's not too difficult to come up with variations on the theme. A search for "tablet stand" at Thingiverse revealed a wealth of concepts. A list of desiderata formed as I scanned the different projects.

The Dual Angle Phone and Tablet Stand by Habs13 (March 19, 2017) caught my eye. I figured that two narrower versions of the stand as shown in the generated image above would have most of the wanted qualities.

To the right is a freehand sketch that I initially drew. I knew that each support would be made out of two J-hooks. Each hook is a flat bar with one end bent back on itself. As far as I know OpenSCAD does not have a bend or fold function, so each hook is the union of a flat cube and a cylinder glued on the end. It will be necessary to hog out most of the cylinder. That's the basic idea shown in the top two shapes of the sketch. In the end, something more complex was done, but this is a good starting point.

There is no freehand drawing in OpenSCAD; everything needs to be sized. The bottom shape on the sketch shows my initial considerations about the size of each support. The important parameters are the two angles, β and γ, at which the tablet will be displayed. Luckily, Habs13 gives the angles explicitly as β = 45° and γ = 70°. Another Thingiverse project, Versatile multi-angle phone / tablet stand" by toddschnack (March 10, 2016) uses the same angles. Some hasty tests with a protractor and my biggest tablet confirmed that these values made sense. That means that angle between the two J-hooks at the top of each support will be α = 65° (high school geometry: the sum of the angles of a triangle is 180°).

The next dimension to consider is the height of the stand. The supports should be as small as possible yet provide enough support so that the tablet will not tip over when the screen is touched at the very top. Even if there is a hook at the bottom which should prevent the tablet from pivoting over the top of the stand, I decided that the length of the long side, L in the drawing, should be 93 mm long which is slightly more than half of the height of the tablet when the latter is in landscape mode. This has worked out rather well but the tablet is on the heavy side and the screen is quite sensitive so a light touch is all that needs to be used.

There is no obvious dimension for the width of the J-hooks, as long as they are wide enough so that each support can be fairly stable. A width of 20 mm works well. As for the thickness, 3 mm gives stiff enough elements when printed in PLA with 20% infill.

Given that L is the length of the long side, what is S the length of the short side? Again this is a simple high school geometry problem. Drop a vertical line from the apex of the triangle to create two right angle triangles within the original one. The length of the vertical is denoted H. The ratio H/L is the cosine of the 45° angle (or the sine of the 45°). So H = L*sin(45°). The ratio H/R is the cosine of the 20° angle (or sine of the 70° angle). So S = H/cos(20°). In other words

    S = H/cos(20) = L*sin(45)/cos(20) = 0.753*L

The length of the short J-hook is three quarters that of the long one. All the other dimensions, thickness, width, size of the hook, are the same. The two hooks are joined at the apex in a 65° angle. At this point, let’s start creating a J-hook in OpenSCAD, and look at how to "bend back" the sheet of plastic.

First J-Hook toc

Let’s tell OpenSCAD to draw the plate and cylinder that will be used to make each leg of the A support.

$fn=32; // thickness of stand (mm) jthick = 2; // the width should be enough so that a single A support can // stand up on its own jwidth = 20; // inside dimension between the leg and the hook // should equal the thickness of the tablet plus a small tolerance jgap = 7; // the length of the arm; does not include the radius of the hook jlength = 93; leg_v1(jlength, jwidth, jthick, jgap); module leg_v1(length, width, thick, gap) { radius = gap/2 + thick; cube([length, width, thick]); // the plate color("red") cylinder(h=width, r=radius); // the hook }

Note how in the (outside) radius of the hook is defined in the first line of the module. It is half the gap plus the thickness of the material to be bent 180°. Instead of the radius, the diameter of the cylinder could have been defined in which case it would have been jgap + 2*thick.

The dimension lines and labels were added manually. Because the CAD program draws cylinders around the z-axis, the hook is in the wrong orientation. Let's rotate the cylinder 270° about the x-axis as shown on the right. That's better but clearly the cylinder has to be moved up in the z-direction until its bottom is just touching the x-y plane in line with the bottom of the bar. This movement is not difficult to calculate, it is simply the radius of the cylinder. The translate function takes care of this.

The leg_v2 module produces the above shape.

module leg_v2(length, width, thick, gap) { radius = gap/2 + thick; cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); }

That whole cylinder is not needed, only the left half is. I do not know if there is a way to produce half a cylinder, but it is possible to remove the extraneous part of the cylinder by creating a cube over the right half of the cylinder above the plate and then using the difference function.

So here is the third version of the module that produced that shape.

module leg_v3(length, width, thick, gap) { radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); } translate([0, 0, thick]) color("pink") cube([radius, width, 2*radius]); } }

More material needs to be removed. A cylinder with a smaller radius drawn within the original cylinder should do the trick. Here is an on-edge representation of the shapes that will be removed.

The smaller blue cylinder is rotated and then translated just like the bigger cylinder; the only difference between the two is that the radius of the smaller cylinder is reduced by the thickness of the plate. In other words, its radius is equal to the gap. Since cylinders are drawn around their principal axis, the smaller cylinder is exactly centred within the bigger one and in effect it transforms the latter into a tube with a thickness equal to that of the plate. The pink "cube" is joined to the blue cylinder to ensure that the hook does not become a complete tube. Here is a revised version of the leg module that includes this new negative element.

module leg_v4(length, width, thick, gap) { radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); } union() { translate([0, 0, radius]) rotate([270, 0, 0]) color("blue") cylinder(h=width, r=gap/2); translate([0, 0, thick]) color("pink") cube([radius, width, 2*radius]); } } }

That is not bad, but those sharp edges at the end of the hook look menacing. A rounded edge is easily added.

translate([0, 0, gap + 3*thick/2]) // or 2*radius-thick/2 rotate([270, 0, 0]) color("orange") cylinder(h=width, d=thick);

Again that is just a cylinder with a diameter equal to the part thickness. It is half buried into the end of the hook to ensure that a smooth transition between the two shapes. I decided to add a lip, which is nothing but a "cube" so that the length of the hook can be extended if desired.

Here is the module that produces the part.

module leg_v7(length, width, thick, gap, lip) { asser(lip >= 0, "lip cannot be a negative"); radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); } union() { translate([0, 0, radius]) rotate([270, 0, 0]) color("blue") cylinder(h=width, r=gap/2); translate([0, 0, thick]) color("pink") cube([radius, width, 2*radius]); } } translate([0, 0, 2*radius- thick] ) { color("purple") cube([lip, width, thick]); translate([lip, 0, thick/2]) rotate([270, 0, 0]) color("orange") cylinder(h=width, d=thick); } }

As can be seen, the module contains no magic numbers except for the rotation angle. Everything else is parametric so that it will be easy to adjust the measurements to obtain a stand appropriate for any tablet or telephone.

First Support toc

Each support is nothing but two J-hooks at a 65° angle with a cross brace. First we begin by drawing and positioning the two legs. That should be fairly easy to do because the shorter leg is almost the same as the longer leg except for its length and its hook has to face away from the hook of the long leg. Rotating the leg 180° about the x-axis will require moving it in back in the y direction so that it is in the same z-plane as the long axis. To simplify, let’s arrange things so that the two legs meet back to back at the origin. That means that both legs will have to be translated along the x-axis. The two translations of the short leg along the x and y-axis can be combined after the leg has been rotated 180°. Finally, the short leg has to be rotated down 65° to create the inverted V shape of the support as can be seen at the right. As can be seen looking at the script, positioning the legs is not that difficult given the leg module that takes care of forming the hook.

// draw long leg pushed along x axis until the unhooked end is at the origin translate([-jlength, 0, 0]) leg(jlength, jwidth, jthick, jgap, jlip); // short leg rotated so the hook is facing the opposite direction // and translated so that its unhooked end is also at the origin rotate([0, -65, 0]) translate([-jslength, jwidth, 0]) rotate([180, 0, 0]) leg(jslength, jwidth, jthick, jgap, jlip);

To complete the model a cylinder is added at the apex where the two legs meet as well as a brace because clearly the support with just two legs joined along a single edge as shown above would not stay in one piece.

Here is the OpenSCAD script that generated the above shape.

$fn=32; // thickness of stand (mm) jthick = 3; // the width should be enough that a single A can // stand up on its own jwidth = 20; // outside radius of the hook jgap = 12; // the overall length of the long leg of the stand // is jlength + jradius jlength = 93; // lip or flat extension of the hook (must be 0 or more mm) jlip = 8; // length of short leg of a support A //jslength = jslength = jlength*sin(45)/sin(70); // or jlength*sin(45)/cos(20); // position of brace along long leg from 5 (almost at the top) // to 100 (almost at the bottom) of the long leg jbracepos = 70; // ************************* // **** Draw the support *** // ************************* // draw long leg pushed along x axis until the unhooked end is at the origin translate([-jlength, 0, 0]) leg(jlength, jwidth, jthick, jgap, jlip); // short leg rotated so the hook is facing the opposite direction // and translated so that its unhooked end is also at the origin rotate([0, -65, 0]) translate([-jslength, jwidth, 0]) rotate([180, 0, 0]) leg(jslength, jwidth, jthick, jgap, jlip); //cylinder to join the two legs at the origin rotate([270, 0, 0]) cylinder(h=jwidth, r=jthick); // calculations for the brace // P how far to translate the brace along the long leg (the x-axis) and // D the length of the brace P = jbracepos*jlength/100; D = (1+tan(20))*P/sqrt(2); // brace translate([-P, 0, 0]) rotate([0, 45, 0]) color("orange") cube([D, jwidth, jthick]); //**** leg (J-hook) module ****// module leg(length, width, thick, gap, lip) { assert(lip >= 0, "lip cannot be a negative"); radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); } union() { translate([0, 0, radius]) rotate([270, 0, 0]) color("blue") cylinder(h=width, r=gap/2); translate([0, 0, thick]) color("pink") cube([radius, width, 2*radius]); } } translate([0, 0, 2*radius- thick] ) { color("purple") cube([lip, width, thick]); translate([lip, 0, thick/2]) rotate([270, 0, 0]) color("orange") cylinder(h=width, d=thick); } }

The only complication is the calculation of the length of the brace. Exactly where the brace is situated is not all that important, but it should nearer the bottom than the apex where both legs of the support meet to increase the stability. The position of the brace is specified as a percentage along the length of the long leg of the support. The closer the value of jpos is set to 0 the nearer the brace will be to the apex. The closer to 100, the closer the brace will be to the surface on which the support rests. Note that even if jpos is set to 100, the brace will not touch the surface on which the support is set because the hook extends the support a bit beyond the jlength. Given this relative position jpos, it is a simple matter to calculate P the distance from the apex to the brace along the long leg: P = jpos*jlength/100.

It takes a little bit of high school geometry and trigonometry to calculate the length of the brace denoted D. It's done in two parts. Since P is the hypotenuse of an isosceles right triangle then, by Pythagoras’s theorem, P² = 2A² from which we get A = P/√2. Again, since the vertical dropped from the apex created an isosceles triangle, the height of the vertical line is A. Now B/A is the tangent of the 20° angle so, B = A*tan(20°) and consequently

    D = A + B = A + A*tan(20°) = (1 + tan(20°))*A = (1 + tan(20°))*P/√2 = 0,965*P

It is important to calculate D, the length of the brace, accurately because if is too short it will not reach the short leg and if it is too long it will poke through the short leg. There really is not much room for error.

Nit pickers among readers (assuming there will be enough of those to attain a population big enough to define subgroups of it) will have noticed that the brace is not truly horizontal. The brace is parallel to the ends of the flat part of each leg. However a hook of exactly the same size is tacked on and since these are at different angles the vertical distance from the ends of the legs and the part of the hook that touches the supporting surface will be different. This could be fixed with a bit of trigonometry, but a built-in healthy laziness easily overcame any desire to avoid this almost unnoticeable error.

This model seemed good enough so I printed it. It turned out that there were some mistakes. I had made an error in measuring the thickness of the tablet on which the radius of the hook was based. The tablet has a thin inside frame that protrudes slightly above the surface of the glass that I had not taken into consideration. The hook was binding on the tablet preventing it from resting on the legs (see the above photo). As a result, the tablet bounces when its surface is touched when at the steeper angle. The stand was usable, but I decided that the hook needed to have a bigger radius. As the curved bottom of the hook increased the chances of binding, it was a good time to redesign the hook so that it offered a flat surface on which the edge of the tablet could rest without risk of pinching along the curved wall.

Second J-Hook toc

To make the task of designing a new hook a little simpler, the shape to be removed is placed in a module called plug.

//**** leg (J-hook) module ****// module leg(length, width, thick, gap, lip) { assert(lip >= 0, "lip cannot be a negative"); radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); translate([0, 0, 2*radius- thick] ) { color("purple") cube([lip, width, thick]); translate([lip, 0, thick/2]) rotate([270, 0, 0]) color("orange") cylinder(h=width, d=thick); } } plug(width, thick, gap, lip); } } module plug(width, thick, gap, lip) { radius = gap/2 + thick; translate([0, 0, radius]) rotate([270, 0, 0]) color("blue") cylinder(h=width, r=gap/2); translate([0, 0, thick]) color("pink") cube([lip+thick, width, gap]); }

To get a flat bottom on the inside of the hook, the plug can be simplified by not removing the interior half cylinder. Only a cube shaped plug is puched out of the cylender and leaving the lip and a flat inside surface.

module plug(width, thick, gap, lip) { translate([0, 0, thick]) color("pink") cube([lip+thick, width, gap]); }

If the tablet to be supported is a slab with right angles or at least has a straight bottom edge at 90° to the surface and back, then this would be the best choice. However all my tablets have rounded edges so a fillet in the inside angles of the hook would not cause any problem. Besides, I feel that the abrupt inside 90° angles are points of weakness. This is just intuition not verified by making a print. It is also an esthetic consideration. Given the overall round shape of the hook, it would be better, in my opinion, to have a concave fillet in each of those two inside edges. Fillets are not easy in OpenSCAD. To convince yourself of that just do a Web search with "openscad fillet". So instead of trying to add fillets, let’s replace the cube in the plug to one with rounded corners. Since the fillet is along only one axis, this is not too difficult.

The shape to be removed is shown below on the left.

As can be seen it is constructed from two cylinders and a cube using the hull function. The latter creates the convex hull (smallest convex set) containing the given 3D objects. I don't think that convex combinations of points was a topic covered in my high school studies, but it is an intuitive concept at least for those with a modicum of visualization skills. Just think of stretching cellophane around the three shapes and filling in the space defined that way without deforming the thin plastic shell.

module plug(width, thick, gap, lip) { translate([0, 0, thick]) hull() { translate([0, 0, thick/2]) rotate([270, 0, 0]) cylinder(h=width, d=thick); translate([0, 0, gap-thick/2]) rotate([270, 0, 0]) cylinder(h=width, d=thick); translate([lip, 0, 0]) color("blue") cube([thick, width, gap]); } }

The only tricky part of this construct was to determine the correct dimensions. As usual a drawing is a better explanation.

Because the whole hull is translated up by the thickness of the leg, measurements in the z direction are taken from the top of the leg plate represented by the red line in the figure on the left. The cube height (along z-axis) is equal to the inside gap of the hook in which the tablet should slide. The cylinders have a diameter equal to the thickness of the hook walls. Since cylinders are drawn around their principal axis, it was necessary to translate the bottom one up in the z direction by a distance equal to its radius (=T/2 on the figure where T is equal to thick). Similarly the top cylinder has to be translated up by a distance equal to the gap less T/2. That's obvious when looking at the cube to the left. The result is that the two cylinders line up with the cube in the vertical direction. Note how this shape will hog out slightly more material from the large cylinder than before. This part, T/2 mm thick, is shown in light gray area on the left figure. The inside corner fillets, which is the material left in place, will have an inside radius equal to half the wall thickness: thick/2.

By Hook or by Crook toc

Just to confirm yet again that there is more ways than one to attain an objective, the plug used to make the hook can be defined as the Minkowski sum of a cube and a cylinder. The resultant sum is shown on the left, the two objects added are shown on the right.

Anticipating what the Minknowski sum of two 3 dimensional shapes will look like is not as easy as visualizing their convex hull. As a first step, consider a two-dimensional shape (the red set S in the x-y plane below) being added to the set {v} which contains the single vector v. The result is the set of all points that can be expressed as the vectorial sum of a point p in S and the vector v = (vx, vy). This is just the translation of the set S along the vector v. When the set S contains the origin then this translation is easily visualized.

Since the origin (0, 0) is in S, then v is also in the Minkowski sum: (0,0)+(vx,vy) = (vx,vy) = v because it has been shown to be the sum of a point in S and v. So S + {v} is the grey shape.

In the case of the plug, the origin is contained in the blue cube, so the cylinder is part of the Minkowski sum. Consider the cube vertex nearest the viewer (point u where x=0 and z=0). Its sum with the cylinder will translate the latter in the y direction only which in effect doubles the height of the cylinder. The original cylinder and the translated cylinder will be the bottom chin of the plug. Similarly, vertex v of the blue cube (where x=0 and y=0) will translate the cylinder up in the z direction only until it just touches the cube along the v-w edge. The vertex w will translate the cylinder to the same heigth but also in the y direction. So that forms the top rounded edge of the plug. Doing this for all points on the surface of the cube is like drawing the latter using the cylinder as the brush. Of course, the sum is solid because both the cube and cylinder are solids. Here is the code for the plug.

// using minkowski sum of a cube and a cylinder module plug2(width, thick, gap, lip) { translate([0, 0, thick]) minkowski() { color("blue") cube([lip+thick, width/2, gap-thick]); translate([0, 0, thick/2]) rotate([270, 0, 0]) cylinder(h=width/2, d=thick); } }

Notice how the dimensions of the cube and cylinder had to be adjusted. They are half the size in the y direction to allow for the sum of the two objects. While the OpenSCAD documentation says that the minkowski transformation is an elegant solution to rounding edges compared to the hull function, there's no doubt that I will need much more experience before it becomes as intuitive.

Take Two toc

This is the final version of the script that was used to print the second version of the stand.

$fn=64; // thickness of stand (mm) jthick = 3; // the width should be enough that a single A can // stand up on its own jwidth = 20; // the size of the gap in hook that wraps around the tablet // the outsize radius of the hook will be jgap/2 + jthick jgap = 14; // the overall length of the long leg of the stand // the true length will be jlength + jgap/2 + jthick jlength = 93; // lip or flat extension of the hook (must be 0 or more mm) jlip = 8; // length of short leg of a support A jslength = jlength*sin(45)/sin(70); // or jlength*sin(45)/cos(20); // position of brace along long leg from 5 (almost at the top) to 100 (almost at the bottom) of the long leg jbracepos = 75; // set to true to have a brace with twice the thickness of the // legs jdoublethickbrace = false; // set to false to print only one support, // to true to print two supports print2 = true; // set to true to print a background bounding rectangle to // check if the object fits on printer bed // make sure to set to false when exporting to STL file printArea = true; // ************************ // **** Draw the stand **** // ************************ if (printArea) { translate([0,0,-0.2]) color("lightblue") cube([220, 220, 0.1], center=true); if (print2) { translate([0,0,-0.1]) color("gray") cylinder(h=0.1, d=170); } else { translate([0,0,-0.1]) color("gray") cylinder(h=0.1, d=130); } } if (print2) { translate([20, 5, 0]) rotate([90, 0, 0]) support(); translate([70, -40, 0]) rotate([90, 0, 0]) support(); } else { translate([52, -25, 0]) rotate([90, 0, 0]) support(); } //**** A shaped support ****// module support() { // draw long leg pushed along x axis until the unhooked end is at the origin translate([-jlength, 0, 0]) leg(jlength, jwidth, jthick, jgap, jlip); // short leg rotated so the hook is facing the opposite direction // and translated so that its unhooked end is also at the origin rotate([0, -65, 0]) translate([-jslength, jwidth, 0]) rotate([180, 0, 0]) leg(jslength, jwidth, jthick, jgap, jlip); //cylinder to join the two legs at the origin rotate([270, 0, 0]) cylinder(h=jwidth, r=jthick); // brace at apex M = 2*jthick; F = (1+tan(20))*M/sqrt(2); translate([-M, 0, 0]) rotate([0, 45, 0]) color("orange") cube([F, jwidth, jthick]); // calculations for the brace // P how far to translate the brace along the long leg (the x-axis) and // D the length of the brace P = jbracepos*jlength/100; D = (1+tan(20))*P/sqrt(2); // brace translate([-P, 0, 0]) rotate([0, 45, 0]) color("orange") cube([D, jwidth, jthick]); if (jdoublethickbrace) { Q = P + jthick; E = (1+tan(20))*Q/sqrt(2); translate([-Q, 0, 0]) rotate([0, 45, 0]) color("orange") cube([E, jwidth, jthick]); } } //**** leg (J-hook) ****// module leg(length, width, thick, gap, lip) { assert(lip >= 0, "lip cannot be a negative"); radius = gap/2 + thick; difference() { union() { cube([length, width, thick]); translate([0, 0, radius]) rotate([270, 0, 0]) color("red") cylinder(h=width, r=radius); translate([0, 0, 2*radius- thick] ) { color("purple") cube([lip, width, thick]); translate([lip, 0, thick/2]) rotate([270, 0, 0]) color("orange") cylinder(h=width, d=thick); } } plug(width, thick, gap, lip); } } //** rounded cube to make trough in hook **// module plug(width, thick, gap, lip) { translate([0, 0, thick]) hull() { translate([0, 0, thick/2]) rotate([270, 0, 0]) cylinder(h=width, d=thick); translate([0, 0, gap-thick/2]) rotate([270, 0, 0]) cylinder(h=width, d=thick); translate([lip, 0, 0]) color("blue") cube([thick, width, gap]); } }

Notice how a new module, called support, was introduced. It takes care of printing a single A-shaped support. The two supports needed when printing a stand can easily be printed together using this function. Note how the orientation of the supports is rotated 90° so that the edge of each support is on the x-y plane (where z=0) which is the obviously best orientation for printing . A couple of flags have been added at the start of the script.

There is another little change with respect to the previous script. A very short horizontal brace is added just under the cylinder at the apex of the support. Hopefully, that makes the point of junction stronger and the straight line at the same angle as that of the brace is more pleasing to the eye.

This time around, the gap in the hook is wide enough to accommodate the tablet which rests entirely on the support legs as can be seen on the photo. Again there was an adhesion problem, I should have printed a skirt to avoid it. Luckily, this has no impact on the use of the stand since each support was printed on its side.

Future Versions toc

I am not entirely satisfied with the fixed angles of the legs. The 45° tilt seems to work well whether sitting at a table or standing at a counter. The 70° tilt feels a bit steep even when sitting down. I think it would be better to rebuild the script with the two angles β and γ defined as adjustable parameters and that should not be too difficult. If that turns out to be too daunting, although I should not think it will, then letting β remain constant at 45° and letting the more problematic γ be defined in a variable (named jgamma) would entail just a few changes to the script. The modified script can be downloaded from the link in the next section.

Some time in the future I will take a fresh look at these scripts and then there will be a good probability that ways to improve them will become obvious. As shown, these scripts are actually modified versions of the actual script used to print the second tablet stand shown in the photograph. When writing up this post, it became clear that changing the position of the leg object simplified all the rotations and translations that were in the original script. The size of the hook used to be defined in terms of its outside radius. Perhaps that contributed to the error with the first print. This corroborates one of the reasons given for this site as these posts provide gains in clarity that comes with trying to explain something to others.

There are a number of things that I may want to change or add in the future. I am looking into using an older Android tablet as a dedicated interface for our home automation server. If this turns out to be worthwhile, I would probably want to build a case that fully contains the tablet and power supply perhaps. However for test purposes a stand with two supports joined with a cross brace would be a good test fixture.

The hook could be reworked as shown on the right. The two Thingiverse projects discussed above used such a shape which has the advantage of reducing the amount of plastic to extrude.

As can be seen, there are many ways in which this project could be improved. Nevertheless, I am quite happy with the stand which has become a part of my morning ritual when I have breakfast on my own.

Downloads toc

For convenience, here are the scripts discussed in this post.

bblock.scad
The first example script showing various aspects of the OpenSCAD language
j-hook.scad
The source for the generated shapes shown in section First J-Hook.
tablet_stand_v1.scad
First printed version of the tablet stand as shown in section First Support.
tablet_stand_v2.scad
Second printed version of the tablet stand as shown in section Take Two.
tablet_stand_v3.scad
Modified version of the previous script that where the angle (jgamma) of the short leg of the A support can be set to a value other than 70°. No print has been done based on this model but it should be correct. Do be careful about the size especially when printing two supports at once with a lower values of jgamma. The ShowArea elements of the script have not been modified to handle the case where the short leg is actually longer than the long leg (when jgamma < 45).
tablet_stand_v1-3.zip
Archive containing the last three files above.
<-First 3D Prints with an ANYCUBIC Mega Zero