Este tutorial pretende ser una iniciación al lenguaje de programación OpenSCAD que permite el diseño de piezas 3D, para que puedan ser impresas posteriormente con una impresora 3D. Se trata de un lenguaje de programación funcional con un número reducido de funciones, lo que hace que sea un lenguaje sencillo, fácil de aprender y adecuado durante la iniciación a la programación.

El objetivo, en este caso, será crear las piezas necesarias para el clásico juego de las Torres de Hanoi.

Si finalizas todos los pasos podrás obtener un diseño similar al siguiente:

Screen Shot 2017 10 09 at 22.39.43 1b4a3

Instalación de OpenSCAD

Descarga o copia el instalador de OpenSCAD, y realiza la instalación estándar de la aplicación.

Ten a mano la documentación de referencia del lenguaje, para que conozcas la manera de usar las distintas funciones que permiten generar los diferentes elementos del diseño. Observa que en la parte inicial de la página de referencia del lenguaje se ofrece una guía general de uso del lenguaje así como un listado con todas las funciones que pueden utilizarse, con enlaces a la descripción y ejemplos de cada función. También dispones de la siguiente chuleta (cheat sheet) que puedes ver mejor pulsando sobre ella:

openSCAD cheat sheet bc028

 

En Wikibooks puedes encontrar el OpenSCAD User Manual, bastante completo, con traducción al español pero con menos contenidos.

También dispones del manual del interfaz gráfico para conocer los distintos paneles que forman la pantalla de la aplicación, las combinaciones de teclado y de ratón para realizar diversas acciones, y algunas opciones de los menús.

Programación del soporte del juego

Uso de funciones en OpenSCAD

Vamos a escribir el código necesario para diseñar la base rectangular del juego. Esto podrás hacerlo usando la función cube (observa siempre la documentación de cada función que se emplea, para conocerla en profundidad), indicando en los parámetros el tamaño que desees darle a la base. Ten en cuenta que una impresora 3D sencilla tiene un tamaño máximo de impresión de 20 cm (200 mm) en los ejes X e Y, así que la base debe ser de un tamaño inferior a esa medida.

Con el siguiente trozo de código se generará un prisma de 150 x 50 x 4 mm.

cube(size = [150, 50, 4]);

Como puedes ver en la documentación de la función cube, también se puede escribir sin indicar el nombre del parámetro (size). Esto es una característica propia de este lenguaje de programación, porque lo habitual es que no se pueda indicar el nombre del parámetro, si no que se tendrán en cuenta el orden en que se escriben dichos parámetros.

cube([150, 50, 4]);

Observa que en la documentación de la función cube se especifica que el primer parámetro corresponde a la medida del prima en el eje X, el segundo corresponde al eje Y y el tercero al eje Z. Acostúmbrate a mirar en la documentación el significado de los parámetros de cada función que se vaya utilizando.

Cada línea de código debe terminar en punto y coma (;).

Las llamadas a las funciones se realizan indicando su nombre seguido entre paréntesis de los parámetros que necesite.

En openSCAD, cuando es necesario indicar una posición concreta del espacio de trabajo se hace utilizando corchetes [ ] incluyendo dentro (separados por comas) las coordenadas X, Y y Z.

Comentarios en el código

Recuerda ir colocando comentarios (abre el enlace para conocer su uso) dentro del código para destacar cada bloque de código. Es frecuente escribir un comentario de varias líneas al principio del archivo donde indiques el objetivo de esta actividad, tu nombre, fecha de creación y tipo de licencia que le otorgas para permitir su distribución. Recuerda que en este lenguaje se utiliza // para los comentarios de una línea y /* */ para los comentarios de varias líneas.

/*
Torres de Hanoi

Diseño 3D de los elementos del juego de las torres de Hanoi.
Desarrollado como ejemplo de programación básica con empleo de
llamadas a funciones, para el módulo profesional de Programación
del ciclo formativo de Desarrollo de Aplicaciones Web del
IES Ntra. Sra. de los Remedios.

Copyright (C)   
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// Base rectangular
cube([150, 50, 4]);

Vista previa del diseño

Utiliza el botón Preview o la tecla F5 para previsualizar el diseño con la orden indicada hasta el momento. Screen Shot 2017 10 09 at 22.53.49 69094

Screen Shot 2017 10 09 at 22.54.56 2f2ce

Diseño de los ejes verticales

Función cylinder

Lee la documentación de la función cylinder para conocer su uso.

Para crear uno de los ejes, se puede diseñar un cilindro de 6.5 mm de radio y 20 mm de alto. Observa que los valores numéricos con decimales se indican con punto (.) y no con coma. 

cylinder(r=6.5, h=20);

Si no se quieren usar los nombres de los parámetros hay que mantener estrictamente el orden que se indica en la documentación de la función, y en este caso el parámetro center es opcional:

cylinder(h = height, r1 = BottomRadius, r2 = TopRadius, center = true/false);

Así que también se podrá poner como:

cylinder(20, 6.5, 6.5);

Screen Shot 2017 10 09 at 23.12.14 a1015

Añade también el parámetro $fn para que el cilindro tenga las paredes más uniformes. En este parámetro se indica el número de caras laterales que va a formar el cilindro. Con un valor de 100 queda bien para este caso.

cylinder(20, 6.5, 6.5, $fn=100);

Función translate

Como has podido comprobar, cada vez que se añade un nuevo elemento al diseño, éste aparece en la posición 0,0,0 del eje de coordenadas. Si se desea mover a otra posición hay que utilizar la función translate. Como siempre, consulta su documentación para conocer su uso.

Puedes ver que se usa una pareja de llaves { } para indicar dentro de ellos el elemento o los elementos que se desean trasladar. Esto hace que quede delimitado el final de la llamada a esta función, por lo que no se utiliza el punto y coma para finalizarla.

Si la base del cilindro es de 150 x 50, el primer cilindro deberá colocarse en la posición x=25, y=25 y a 4 mm de alto (eje Z), ya que es ese el tamaño de la base en ese eje.

// Cilindro izquierdo
translate([25, 25, 4]) {
    cylinder(20, 6.5, 6.5, $fn=100);
}

 Screen Shot 2017 10 09 at 23.23.52 085db

Segundo y tercer eje

Los otros 2 ejes se crearán y colocarán de manera similar al primero.

En todos los casos, la posición en el eje Y deberá ser la mitad del tamaño Y de la base (si el tamaño Y era 50 mm, la posición Y debe ser 25). Hay que recordar que la posición de los cilindros está establecido por defecto en el centro de su base, en lugar de una de sus esquinas como sí pasa con la función cube.

La posición X del segundo eje debe ser la mitad del tamaño X de la base (si el tamaño X era 150 mm, la posición X debe ser 75). Por otro lado, la posición X del tercer eje deberá estar a 25 mm (la mitad del tamaño Y) del final de la base, por lo que se coloca en la posición 125 (150 - 25).

posicionesEjes 186d1

//Cilindro central
translate([75, 25, 4]) {
    cylinder(20, 6.5, 6.5, $fn=100);
}

//Cilindro derecho translate([125, 25, 4]) { cylinder(20, 6.5, 6.5, $fn=100); }

Screen Shot 2017 10 10 at 21.01.26 43776 

Diseño de los discos

Disco mayor

Usando la función cylinder que ya conoces, se pueden diseñar los discos que completan el juego de las Torres de Hanoi. El tamaño del disco más grande se va a diseñar de manera que sea tan sólo algo más pequeño que el tamaño Y de la base (50 mm), para que al colocar el disco en alguno de los ejes, no sobresalga de la base. Por ejemplo, se puede establecer que tenga un tamaño de 45 mm de diámetro. Si recuerdas la documentación de la función cylinder, el tamaño del cilindro se puede establecer indicando su radio (r) o bien su diámetro (d). Para que pruebes los distintos usos, vamos a diseñar el disco especificando su diámetro:

cylinder(h=3, d=45, $fn=100);

Observa que se ha especificado también que el tamaño Z sea 3 mm. Ese tamaño debe cumplir la condición de que el tamaño total que ocupen todos los discos, sea inferior al tamaño Z de los ejes, de manera que todos los discos que se vayan a utilizar se puedan apilar.

Screen Shot 2017 10 10 at 21.15.09 ac466

Hueco del disco

Para crear el hueco central de los discos, debes usar la función difference. Como siempre, observa los ejemplos y la documentación de la función difference para conocer su funcionamiento. En resumen, dentro de las llaves se debe incluir en primer lugar el objeto al que se le va a restar el objeto que se indique en segundo lugar.

El hueco debe tener un diámetro que sea ligeramente mayor que el tamaño de los ejes para que los discos puedan colocarse cómodamente en ellos. A los ejes se les había asignado un radio de 6.5 mm (13 mm de diámetro), por lo que el hueco de los ejes deberá ser 1 o 2 mm de diámetro mayor. Para que se vea de manera clara el cilindro que se va a restar al disco, vamos a colocarlo antes de indicar la función difference y con un tamaño Z sensiblemente mayor para que destaque:

cylinder(h=3, d=45, $fn=100);
cylinder(h=6, d=14, $fn=100);

 Screen Shot 2017 10 10 at 21.25.17 7155c

Ahora, si esas mismas sentencias se encierran dentro de la función difference, se restará el segundo cilindro al primero:

difference() {
    cylinder(h=3, d=45, $fn=100);
    cylinder(h=6, d=14, $fn=100);
}

Screen Shot 2017 10 10 at 21.27.42 b69d9

 

Posicionamiento del disco 

Evidentemente, el disco debe colocarse en otra posición, porque ahora mismo aparece formando parte de la base. Para colocarlo fuera, se usa la función translate de la que se ha hablado anteriormente.

Deberá moverse en el eje X de manera que el borde del disco quede en la misma posición que el lateral izquierdo de la base, con el fin de que si se lleva el modelo a una impresora 3D quede todo el conjunto ocupando el menor espacio posible. Por tanto, si el diámetro del disco es 45, y su centro está ahora mismo en el eje, se debe desplazar a la derecha la mitad de su tamaño, es decir 45 / 2.

Screen Shot 2017 10 10 at 21.35.58 77cc1

 

Por otro lado, hay que cambiar la posición en el eje Y para que quede fuera de la base. Si el tamaño Y de la base es 50, el radio del disco es 45/2 y se desea dejar un poco de separación (unos 5 mm) entre la base y el disco, la posición sera: 50 + 45/2 + 5.

// Disco 1 (mayor)
translate([45/2, 50 + 45/2 + 5, 0]) {
    difference() {
        cylinder(h=3, d=45, $fn=100);
        cylinder(h=6, d=14, $fn=100);
    }
}

Screen Shot 2017 10 10 at 21.39.06 51d6f

Como puedes ver, en este lenguaje de programación se pueden utilizar expresiones matemáticas usando los mismos operadores que en otros lenguajes para realizar sumas (+), restas (-), multiplicaciones (*) o divisiones (/). 

Este lenguaje, como es común en los lenguajes de programación, permite el uso de variables que facilitan la lectura del código y la modificación de los valores que se utilicen a lo largo del código fuente de un mismo proyecto.

Por ejemplo, se pueden declarar algunas variables para valores significativos, que luego pueden utilizarse en los cálculos o como parámetros de las llamadas a las funciones:

// Disco 1 (mayor)
diametroDisco1 = 45;
posicionDisco1 = 50 + diametroDisco1 / 2 + 5;
translate([diametroDisco1 / 2, posicionDisco1, 0]) {
    difference() {
        cylinder(h=3, d=diametroDisco1, $fn=100);
        cylinder(h=6, d=14, $fn=100);
    }
}

Añadir más discos

De manera similar se añadirán más discos para que pueda ser jugable. En principio se va a generar un total de 4 discos, pero se puede ampliar con más discos para aumentar la dificultad del juego.

Podemos empezar creando los otros 3 discos del mismo tamaño, pero luego se debe reducir sus tamaños de manera progresiva. La posición que ocupen esos nuevos discos se podrían indicar con la función translate, indicando posiciones absolutas (contadas a partir del eje de coordenadas) como se ha hecho para el primer disco, o bien podemos indicar sus posiciones de manera que sean relativas a la posición del disco anterior. Esto último se puede hacer encadenando las funciones translate unas dentro de otras como puedes ver a continuación. Cada disco se va a desplazar en el eje X un espacio correspondiente al tamaño (diámetro) de los discos más un pequeño espacio de margen (p.e. 5 mm) y observa que no se va a hacer ninguna traslación en el eje Y, ya que se tomará la que se hizo desde el primer disco.

// Disco 1 (mayor)
diametroDisco1 = 45;
posicionDisco1 = 50 + diametroDisco1 / 2 + 5;
translate([diametroDisco1 / 2, posicionDisco1, 0]) {
    difference() {
        cylinder(h=3, d=diametroDisco1, $fn=100);
        cylinder(h=6, d=14, $fn=100);
    }
    // Disco 2
    translate([diametroDisco1 + 5, 0, 0]) {
        difference() {
            cylinder(h=3, d=diametroDisco1, $fn=100);
            cylinder(h=6, d=14, $fn=100);
        }        
        // Disco 3
        translate([diametroDisco1 + 5, 0, 0]) {
            difference() {
                cylinder(h=3, d=diametroDisco1, $fn=100);
                cylinder(h=6, d=14, $fn=100);
            }   
            // Disco 4
            translate([diametroDisco1 + 5, 0, 0]) {
                difference() {
                    cylinder(h=3, d=diametroDisco1, $fn=100);
                    cylinder(h=6, d=14, $fn=100);
                }        
            }
        }
    }
}

Screen Shot 2017 10 10 at 22.05.39 a256d 

Toca ahora ir reduciendo los tamaños de cada disco para cumplir con el objetivo real del juego. Para ello, se puede crear una variable para el tamaño de cada disco, de manera que sea algo menor que el disco anterior (p.e. 5 mm):

// Disco 1 (mayor)
diametroDisco1 = 45;
posicionDisco1 = 50 + diametroDisco1 / 2 + 5;
translate([diametroDisco1 / 2, posicionDisco1, 0]) {
    difference() {
        cylinder(h=3, d=diametroDisco1, $fn=100);
        cylinder(h=6, d=14, $fn=100);
    }
    // Disco 2
    diametroDisco2 = diametroDisco1 - 5;
    translate([diametroDisco2 + 5, 0, 0]) {
        difference() {
            cylinder(h=3, d=diametroDisco2, $fn=100);
            cylinder(h=6, d=14, $fn=100);
        }        
        // Disco 3
        diametroDisco3 = diametroDisco2 - 5;
        translate([diametroDisco3 + 5, 0, 0]) {
            difference() {
                cylinder(h=3, d=diametroDisco3, $fn=100);
                cylinder(h=6, d=14, $fn=100);
            }   
            // Disco 4
            diametroDisco4 = diametroDisco3 - 5;
            translate([diametroDisco4 + 5, 0, 0]) {
                difference() {
                    cylinder(h=3, d=diametroDisco4, $fn=100);
                    cylinder(h=6, d=14, $fn=100);
                }        
            }
        }
    }
}

Screen Shot 2017 10 10 at 22.14.57 12e1b 

Completa el proyecto

Con el resultado obtenido en este punto ya se pueden obtener los elementos mínimos para que sea jugable, pero realiza las siguientes ampliaciones para mejorar su estética y que el código sea más flexible permitiendo modificar los tamaños cómodamente:

  • Crea una variable para cada dato significativo y evita utilizar valores literales en los parámetros de las llamadas a las funciones, usando las variables en su lugar. Por ejemplo, crea una variable para el tamaño Y de la base y que todos los elementos del juego se adapten a ese tamaño.
  • Utiliza la sentencia condicional if para comprobar que el tamaño más grande de la base no supere los 20 cm, o que los discos no sean muy finos o muy gordos En caso de que sea mayor, se puede mostrar un mensaje en la consola utilizando la función echo.
  • Utiliza también un bucle for para que la base se genere de forma escalonada como se puede ver en la imagen del inicio del artículo. El bucle debe dar tantas vueltas como milímetros tenga el grosor de la base. Por cada milímetro se generará un cubo nuevo, reduciendo su medida 1 mm cada vez por cada lado.
    • Para conocer mejor el funcionamiento de los bucles for en OpenScad mira los primeros ejemplos que aparecen en el apartado For loop del artículo OpenSCAD User Manual/Conditional and Iterator Functions.
    • En el artículo Ejemplo de bucle for - Cono de cilindros puedes encontrar un ejemplo sencillo de uso del bucle for.
    • También en el artículo OpenSCAD::Loops de spolearninglab.com puedes encontrar varios ejemplos de uso.
  • Ofrece la posibilidad de generar más de 4 discos. Para ello, asigna un valor a una nueva variable donde se indique el número de discos “extra” que se quieren generar. Utilizando un bucle, se generarán esos discos en una nueva fila, por detrás de los 4 discos obligatorios, como se puede ver en la imagen inicial.
  • Añade un texto a la parte superior de la base, de manera que quede ahuecado, sin que llegue el hueco hasta el fondo, como se muestra en la imagen inicial.
  • Recorta las esquinas de la base usando la función de transformación minkowski.
  • Y todo lo que se te ocurra!!!

 

numPisos = 10;
altoPiso = 2;

// Número de caras a utilizar en todos los cilindros $fn =100; // for(variable = [start : increment : end]) for(i = [0 : 1: numPisos-1]) { translate([0, 0, i*altoPiso]) { cylinder(h=altoPiso, d=numPisos-i); } }

openscadFor 70aea