Tinkercad es una plataforma de diseño online muy intuitiva para crear piezas y diseños de forma rápida. Si lo que queremos es hacer un muñéco o piezas simples con unas medidas concretas; podemos hacerlas sin necesidad de amplios conocimientos en diseño.

Otras herramientas de diseño como FreeCAD o OpenSCAD suponen una transición hacia el modelado paramétrico.

¿Qué es el modelado paramétrico?

El modelado paramétrico es una forma de crear modelos; como indica la propia palabra; en función de parámetros definidos. Es decir, si yo quiero hacer el modelo de una persona, podría introducir su figura en un entorno de diseño y modificar su estatura o su anchura de manera visual. Pero de esta manera crearía solamente una variante de ese modelo.

El diseño paramétrico son un conjunto de reglas en el que se pueden definir esa estatura y anchura a través de un interfaz y el resto de parámetros se adaptan de la manera que nosotros hemos definido.

Tinkercad en un principio no es una herramienta principalmente paramétrica, pero ofrece una API en el que el usuario puede programar mediante Javascript figuras e introducirlas dentro del programa en una sección denominada Shape Generators para programar nuestras piezas mediante parámetros.

Para unas clases con unos alumnos yo voy a crear un par de piezas que estarán publicadas para su uso.

En primer lugar crearé una pieza que genere un trapecio con puntas redondeadas y que se llamará “Circular Trapezoid”.

Y a partir de esta primera pieza, generaré otra seleccionable llamada “Servo Horn Hole” para modelar los anclajes estandarizados de unos servomotores.

De esta manera si queremos hacer el hueco para encajar un servomotor en una pieza; solo tendremos que elegir el motor que vamos a usar y una de las distintas opciones de los anclajes que vienen con los servos.

Para ello, nos tendremos que relacionar un poco con ese modo de programación de piezas. Nos creamos una cuenta en Tinkercad y al lio.

Circular Trapezoid

Para empezar, tendremos que crear un nuevo diseño presionando la tecla “Create New Design” y una vez dentro del entorno de edición dirigirnos a la sección Shape Generators –> Your Shape Generators y creamos una nueva pieza con el botón “New Shape Generator”.

Una vez que creada una pieza, aún no aparecerá nada; ya que no hemos creado nada; pero habremos de especificar un nombre y una descripción para información de los usuarios.

  • Settings –> Edición del nombre y descripción de la pieza
  • Libraries –> Aquí se supone que hay librerías compartidas; pero en principio no hay nada más que la desarrollada por Autodesk para la creación de esta API.
  • Scripts –> Aquí será donde guardaremos nuestros códigos javascript. En principio solo usaremos el main.js que se crea por defecto; pero si el proyecto se hace largo conviene modularlo convenientemente.
  • Resources –> En esta sección se pueden subir fotos del modelo.

NameDesc

El apartado que más nos interesa es la creación del código en javascript a través de la sección scripts.

En este apartado hemos de diferenciar dos pasos en la generación de piezas.

  • Declaración de parámetros –> La primera es la creación de una interfaz para los parámetros que el usuario puede modificar y que afecta a la geometría de la pieza.
  • Procesado del modelo –> Generación de la geometría en función de los valores proporcionados por el usuario.

La realización de estos pasos se puede hacer de dos maneras diferentes:

  • Síncrona –>
  • Asíncrona –> El modelo asíncrono es generalmente utilizado cuando los parámetros son demasiado complicados y requieren de un procesado. Existen funciones que solo pueden funcionar mediante este modelo.

Utilizaremos el modo asíncrono que se basa en la ejecución de dos funciones con el siguiente formato:


function shapeGeneratorDefaults(callback) {
    var params =[
    ]
}

function shapeGeneratorEvaluate(params, callback) {

}

Pero para ser más limpios vamos a iniciar esta programación orientada a objetos; tal y como se explica al final de la sección introductoria Shape Generators Overview; en lugar de crear funciones sueltas para realizar estos dos pasos para la generación de piezas.

Este modelo orientado a objetos trata de crear una variable denominada Generator con dos propiedades que contendrán exactamente el contenido de declaración y generación.

  • parameters –> Función que crear una lista de declaración de parámetros.
  • evaluate –> Función de generación de la pieza.

Finalmente utilizaremos una función denominada shapeGenerator que lo único que hace es devolvernos el objeto en concreto con un return y la pieza aparecerá.

 

La creación de la interfaz se genera dentro de la función parameters que crea una variable denominada params y creando un array se define un identificador de los parámetors para su posterior manipulación.

Estos son los distintos tipos de parámetros que podemos definir dentro de nuestra programación.

  1. Angle  → Refer toAngleParameterJSON for additional fields. → “angle”
  2. Boolean  → Refer toBooleanParameterJSON for additional fields. → “bool”
  3. Real Value  Refer toFloatParameterJSON for additional fields. → “float”
  4. Integer Value  → Refer toIntegerParameterJSON for additional fields. → “int”
  5. Length  Refer toLengthParameterJSON for additional fields. → “length”
  6. List  Refer toListParameterJSON for additional fields. → “list”
  7. 2D Sketch  Refer toSketchParameterJSON for additional fields. → “sketch”
  8. String  Refer toStringParameterJSON for additional fields. → “string”
  9. File  Refer toFileParameterJSON for additional fields. → “file”
parameters: function(callback) {
   var params = [
      {
         "id": "r1",
         "displayName": "Top radius",
         "type": "float",
         "rangeMin": 1,
         "rangeMax": 100,
         "default": 20.0
      },
      {
        "id": "r2",
        "displayName": "Bottom radius",
        "type": "float",
        "rangeMin": 1,
        "rangeMax": 100,
        "default": 20.0 },
      {
        "id": "length",
        "displayName": "Length",
        "type": "float",
        "rangeMin": 1,
        "rangeMax": 100,
        "default": 20.0 }
   ];

   callback(params);
}

Un aspecto importante del que hablaremos en el desarrollo de este ejercicio es la creación del vector normal para la creación de una pieza. Si nos ponemos a pensar una pieza para generarse debe estar definido por una serie de puntos. Pero estos puntos están conectados entre sí mediante aristas; y la unión de estas aristas generan las caras.

Si tuvieramos que pensar en una figura, en principio pensariamos en una pieza muy básica sin agujeros, y los puntos se conectarian en función del más cercano. Pero no existe una solución única dados unos puntos, así como tampoco hay una solución única en la definición de caras. Es por ello que este tipo de programación no es tan evidente y veremos en que se basan. Por ahora comentaremos la posibilidad de definir el vector normal de una cara en función de la regla de la mano derecha. Si las aristas que crean la cara se desarrollan siguiendo una dirección; su vector normal se dirigirá en un sentido y en caso contrario en el sentido opuesto.

surfaceNormal

Para ir paso por paso; vamos a crear primero un trapecio, después un cilindro y finalmente los uniremos para ver el resultado final.

Lo que hay que tener en cuenta son las dimensiones y las posiciones que vamos a tomar respecto del origen para generar un croquis en 2 dimensiones y extruirlo. Si no los hacemos convenientemente; nos pueden aparecer muchos errores, ya que deberemos definir el camino seguido por las lineas desde un punto hasta otro.

 

Por otra parte, es conveniente precisar el origen que será desde el cual se van a realizar giros respecto a los 3 ejes coordenados. Esto será importante posteriormente para crear los anclajes de los servos. Si no se define bien el eje de giro, podemos caer en el error de girarlo respecto a una posición que no nos conviene.

Para la creación del trapecio, definimos el camino seguido entre los diferentes puntos. Primero avanzaremos desde P1 – P2 – P3 – P4 y cerraremos finalmente con P1 de nuevo.

El código para crear un trapecio es el siguiente.


var Conversions = Core.Conversions;
var Debug = Core.Debug;
var Path2D = Core.Path2D;
var Point2D = Core.Point2D;
var Point3D = Core.Point3D;
var Matrix2D = Core.Matrix2D;
var Matrix3D = Core.Matrix3D;
var Mesh3D = Core.Mesh3D;
var Tess = Core.Tess;
var Solid = Core.Solid;
var Vector2D = Core.Vector2D;
var Vector3D = Core.Vector3D;

var Generator = {

   parameters: function(callback) {
      var params = [
         { "id": "r1", "displayName": "Top radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 },
         { "id": "r2", "displayName": "Bottom radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 },
         { "id": "length", "displayName": "Length", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 }
      ];
      callback(params);
   },

   evaluate: function(params, callback) {
      var path = new Path2D();
      //creacion del trapezoide
      var r1 = params["r1"];
      var r2 = params["r2"];
      var l = params["length"];

      path.moveTo(r1,0);
      path.lineTo(-r1,0);
      path.lineTo(-r2,l);
      path.lineTo(r2,l);
      path.close();
      var solid = Solid.extrude([path], 20);

      var trap_mesh = solid.mesh;
      trap_mesh.debug();

      var s = Solid.make(trap_mesh);
     callback(s);
   }
};

// Returns the object-oriented shape generator,
function shapeGenerator() {
   return Generator;
}

Trapezoid

Como podremos ver, la función debug nos proporciona unas lineas que nos indican como se crean las caras en triángulos. Esto es importante para saber definir posteriormente las uniones con respecto a los semicírulos que crearemos a continuación.


var Conversions = Core.Conversions;
var Debug = Core.Debug;
var Path2D = Core.Path2D;
var Point2D = Core.Point2D;
var Point3D = Core.Point3D;
var Matrix2D = Core.Matrix2D;
var Matrix3D = Core.Matrix3D;
var Mesh3D = Core.Mesh3D;
var Tess = Core.Tess;
var Solid = Core.Solid;
var Vector2D = Core.Vector2D;
var Vector3D = Core.Vector3D;

var Generator = {

   parameters: function(callback) {
      var params = [
         { "id": "r1", "displayName": "Top radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 },
         { "id": "r2", "displayName": "Bottom radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 },
         { "id": "length", "displayName": "Length", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 }
      ];
      callback(params);
   },

   evaluate: function(params, callback) {
      var path = new Path2D();
      //creacion del trapezoide
      var r1 = params["r1"];
      var r2 = params["r2"];
      var l = params["length"];

      var pi2 = 2.0 * Math.PI;
 
      //Definición de divisiones angulares
      var lod1 = Tess.circleDivisions(r1);
      var step1 = pi2 / lod1;
      for(var a = 0; a < pi2; a += step1) {
        path.lineTo(Math.cos(a)*r1, Math.sin(a)*r1);
      }
      path.close();
      var solid = Solid.extrude([path], 20);
      
      
      var c1_mesh = solid.mesh;
      c1_mesh.debug();
      var s = Solid.make(c1_mesh);
      callback(s);
   }
};

// Returns the object-oriented shape generator,
function shapeGenerator() {
   return Generator;
}

Circle
Si nos fijamos bien en el debug de esta pieza, podríamos pensar que las caras se crean desde el centro en un intervalo de 0 a 2*pi radianes que es la medida de una circunferencia, pero en este caso es totalmente falso, principalmente porque dentro del bucle solo se pueden hacer iteraciones con medidas de enteros y es por ello que se establece un número dentro del rango a 2*pi radianes con la función Tess. Esta función establece un número de divisiones adecuado para una circunferencia en función del radio .

Ahora vamos a modificar el valor del bucle a 3*pi/4 y podremos ver el error de generación de este cilindro.

      for(var a = 0; a < 3*pi2/4; a += step1) {
        path.lineTo(Math.cos(a)*r1, Math.sin(a)*r1);
      }

CircleFailLa figura no se genera en función de su ángulo; sino que se crea desde un extremo hasta su final en una dirección fija; como se puede observar, este modo de generación de caras no es para nada conveniente y en el momento de realizar uniones con otras piezas nos va a crear un desorden que nos dará errores en su creación.

En el siguiente código se puede realizar la prueba; y se puede apreciar que a medida que se modifican los parámetros, de repente nos proporciona el siguiente error.

Inward pointing normals detected by right hand rule on meshes (meshes[1],meshes[2]).


// Convenience Declarations For Dependencies.
// 'Core' Is Configured In Libraries Section.
// Some of these may not be used by this example.
var Conversions = Core.Conversions;
var Debug = Core.Debug;
var Path2D = Core.Path2D;
var Point2D = Core.Point2D;
var Point3D = Core.Point3D;
var Matrix2D = Core.Matrix2D;
var Matrix3D = Core.Matrix3D;
var Mesh3D = Core.Mesh3D;
var Tess = Core.Tess;
var Solid = Core.Solid;
var Vector2D = Core.Vector2D;
var Vector3D = Core.Vector3D;

var Generator = {

   parameters: function(callback) {
      var params = [
        { "id": "r1", "displayName": "Top radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 15.0 },
        { "id": "r2", "displayName": "Bottom radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 10.0 },
        { "id": "length", "displayName": "Length", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 }
      ];
      callback(params);
   },

   evaluate: function(params, callback) {
      var path = new Path2D();
      //Trapezoid
      var r1 = params["r1"];
      var r2 = params["r2"];
      var l = params["length"];

      path.moveTo(r1,0);
      path.lineTo(-r1,0);
      path.lineTo(-r2,l);
      path.lineTo(r2,l);
      path.close();
      var solid1 = Solid.extrude([path], 20);

      var trap_mesh = solid1.mesh;
      trap_mesh.debug();

      var s1 = Solid.make(trap_mesh);

      var pi2 = 2.0 * Math.PI;

      //Definición de divisiones angulares
      var lod1 = Tess.circleDivisions(r1);
      var step1 = pi2 / lod1;
      for(var a = pi2/2; a < pi2; a += step1) {
        path.lineTo(Math.cos(a)*r1, Math.sin(a)*r1);
      }
      path.close();
      var solid2 = Solid.extrude([path], 20);

      var c1_mesh = solid2.mesh;
      c1_mesh.debug();
      var s2 = Solid.make(c1_mesh);

     //Definición de divisiones angulares
      var lod2 = Tess.circleDivisions(r2);
      var step2 = pi2 / lod2;

      for( a = 0; a < pi2/2; a += step2) {
        path.lineTo(Math.cos(a)*r2, Math.sin(a)*r2+l);
      }
      path.close();
      var solid3 = Solid.extrude([path], 20);

      var c2_mesh = solid3.mesh;
      c2_mesh.debug();
      var s3 = Solid.make(c2_mesh);
      //Creacion del objeto Mesh3D del trapezoide
      callback(s3);
    }
};

// Returns the object-oriented shape generator,
function shapeGenerator() {
   return Generator;
}

TrapezoidFail

Y he aquí la importancia de la que hablabamos antes acerca de las normales de las superficies creadas en una pieza o figura. En otros programas de generación de piezas como OpenSCAD u otros frameworks para crear figuras en 3D como Three.js y otros, se basan en la regla de la mano derecha para crear las caras con una normal hacia fuera o hacia dentro. En este caso; al crear 3 figuras en 2D, extruirlas y unirlas producen estos problemas. Principalmente porque la generación de caras las crea como le da la gana y que imagino que seguirá algún criterio en función de los valores de los parámetros introducidos.

Así que hay que buscar una solución mejor.

En este caso, no nos vamos a complicar la vida creando diferentes maneras para elaborar las caras; sino que vamos a crear un croquis en 2 dimensiones siguiendo un camino directo para el sketch.

En el siguiente post, elaboraremos un diseño para los anclajes de los servomotores con esta primera pieza realizando iteraciones; pero como hemos visto la unión de diferentes conjuntos creará errores.

Aquí dejo el código de generación de la pieza sin fallos y que tambien podréis ver publicada en Tinkercad.

var Conversions = Core.Conversions;
var Debug = Core.Debug;
var Path2D = Core.Path2D;
var Point2D = Core.Point2D;
var Point3D = Core.Point3D;
var Matrix2D = Core.Matrix2D;
var Matrix3D = Core.Matrix3D;
var Mesh3D = Core.Mesh3D;
var Tess = Core.Tess;
var Solid = Core.Solid;
var Vector2D = Core.Vector2D;
var Vector3D = Core.Vector3D;

var Generator = {

parameters: function(callback) {
var params = [
{ "id": "r1", "displayName": "Top radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 15.0 },
{ "id": "r2", "displayName": "Bottom radius", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 10.0 },
{ "id": "length", "displayName": "Length", "type": "float", "rangeMin": 1, "rangeMax": 100, "default": 20.0 }
];
callback(params);
},

evaluate: function(params, callback) {
var path = new Path2D();
//Piece
var r1 = params["r1"];
var r2 = params["r2"];
var l = params["length"];

var pi2 = 2.0 * Math.PI;
var lod1 = Tess.circleDivisions(r1);
var step1 = pi2 / lod1;
var lod2 = Tess.circleDivisions(r2);
var step2 = pi2 / lod2;

path.moveTo(-r1,0);
for(var a = pi2/2; a < pi2; a += step1) {
path.lineTo(Math.cos(a)*r1, Math.sin(a)*r1);
}
path.lineTo(r1,0);

path.lineTo(r2,l);

for( a = 0; a < pi2/2; a += step2) {
path.lineTo(Math.cos(a)*r2, Math.sin(a)*r2+l);
}

path.lineTo(-r2,l);
path.close();
var solid = Solid.extrude([path], 20);

var mesh_piece = solid.mesh;
mesh_piece.debug();

var s = Solid.make(mesh_piece);
callback(s);

}
};

// Returns the object-oriented shape generator,
function shapeGenerator() {
return Generator;
}