laderas con OpenSCAD

Para suavizar la transición entre dos planos paralelos a distintas alturas podemos hacer un chaflán con una esquina y un rincón, o bien hacer uso de dos superficies cilíndricas tangentes entre sí y a los planos.

El siguiente esquema nos ayudará a plantear el problema: tenemos un rectángulo de ancho por alto, en el que queremos inscribir una ladera que empiece y acabe horizontal, y tenga el punto de inflexión en (xi, yi) formando un ángulo v con la vertical.

Para solucionar el problema necesitamos la fórmula de la elipse, pero haciendo que su extremo superior sea tangente al eje de las X (la fórmula tradicional centra la elipse en el origen de coordenadas):

x2/a2 + (y+b)2/b2 = 1

Despejando y:

y1 = b · (+raiz(1 - x2/a2) - 1) [1]

y2 = b · (-raiz(1 - x2/a2) - 1) (esta no nos interesa)

La derivada de y es:

y' = - (b·x) / ( a · raiz(a2 - x2) )

Así, en el punto de inflexión tendremos:

tan(v) = - (b·x) / ( a · raiz(a2 - x2) ) [2]

El sistema de ecuaciones formado por [1] y [2] nos permite despejar a y b que son nuestras incógnitas:

a = raiz( ( x·tan(v) - y )2 / ( tan2(v) - 2·y·tan(v)/x ) )

b = y / ( raiz(1-x2/a2) - 1)

Trasladar estas ecuaciones a OpenSCAD no es inmediato por tres razones:

  • el ángulo v tiene limitaciones importantes, y debe estar entre 90º y un valor que depende de la geometría y hay que calcularlo.

  • OpenSCAD no facilita los cálculos como lo puede hacer un lenguaje de programación convencional, ni permite hacer uso de estructuras de control en las funciones, así que estéticamente dejará mucho que desear.

  • no tengo ni idea de usar OpenSCAD, más allá de lo que se aprende por ensayo y error.

Para facilitar el uso de esta técnica he preparado un módulo que construye una ladera a partir de 6 parámetros:

  • tarugo: vector con las dimensiones que necesitamos como [x, y, z].

  • inflexión_x: proporción de X donde ponemos el punto de inflexión (de 0 a 1, por defecto 0,5).

  • inflexión_z: proporción de Z donde ponemos el punto de inflexión (de 0 a 1, por defecto 0,5).

  • suavidad: suavidad de la inflexión: 0 supone un ángulo v de 90º, y 1 el ángulo mínimo posible (por defecto 1).

  • sobrado: para un uso sustractivo de la ladera (por defecto no, ver ejemplo más adelante)

  • center: indica si centramos la pieza o no (por defecto no).

Estos son algunos ejemplos de uso:

  • en la primera fila se juega con el parámetro suavidad: tope, medio y mínimo.

  • en la segunda se juega con la posición del punto de inflexión

  • y en la tercera son los casos extremos de desplazamiento de la inflexión

Para terminar, el código y un ejemplo práctico en el que veremos cómo hacer un tarugo con forma de chicane. La curva de un lado se hace añadiendo una ladera a un hueco que queda entre dos cubos. La curva del otro lado se hace restando una ladera a un escalón saliente.


Para evitar efectos indeseables cuando se restan piezas del mismo ancho, se usa el parámetro sobrado=true, que hace el tarugo de la ladera algo mayor, pero respetando las cualidades deseables de las curvas.

$fs=.1 ;

$fa=.1 ;



largo = 100 ;

ancho = 30 ;

alto = 20 ;


transicion = 40 ;

desviacion = 10 ;


// uso aditivo de la ladera, volteándola para darle la orientación requerida

translate([-(largo+transicion)/4, -desviacion/2])

cube([(largo-transicion)/2, ancho, alto], center=true);

translate([0,-ancho/2,0])

rotate([90,0,0])

ladera([transicion, alto, desviacion], center=true);


// uso sustractivo de la ladera: el resultado mejora con "sobrado=true"

difference() {

translate([(largo-transicion)/4, desviacion/2 , 0])

cube([(largo + transicion) /2, ancho, alto], center=true);

translate([0,ancho/2,0])

rotate([90,0,0])

ladera([transicion, alto, desviacion], center=true, sobrado=true);

}




module ladera(tarugo, inf_x=.5, inf_z=.5, suavidad=1, sobrado=false, center=false) {

/* planteamiento:

Se trata de devolver un cubo de dimensiones [ tarugo[0], tarugo[1], tarugo[2] ]

recortado formando una vertiente que va de derecha a izquierda, idealmente un

par de curvas cóncava-convexa.

El punto de inflexión se encontrará a inf_x * tarugo[0] del lado izquierdo

y a inf_z * tarugo[2] del borde superior.

La inflexión tendrá un ángulo v con la vertical que estará entre un valor máximo

de 90º (con suavidad==0) y un valor mínimo calculado por tanteo con la función

amin() (con suavidad==1)

Como caso particular, si el punto de inflexión es superior-izquierda tendremos

una curva cóncava con inicio brusco y final horizontal, y si es inferior-derecha

tendremos una curva convexa con inicio horizontal y final abrupto.

El parametro "sobrado" se pone a true cuando vamos a usar la ladera de forma

sustractiva: mantiene las curvas calculadas para el tarugo, que se hace algo

sobredimensionado para evitar efectos indeseables

*/

function acota(v, m, M) = v<m ? m : (v>M ? M : v);

function Ad2(tanu) = pow(tanu*Xd - Zd, 2) / (pow(tanu,2) - 2*Zd*tanu/Xd);

function Ai2(tanu) = pow(tanu*Xi - Zi, 2) / (pow(tanu,2) - 2*Zi*tanu/Xi);

function amin(t=45, m=0, M=90) // búsqueda recursiva del angulo minimo

= ( (Zd==0 || Ad2(tan(t))>0) && (Zi==0 || Ai2(tan(t))>0) )

? ( (abs(t-m)<umbral) ? t : amin((m+t)/2, m, t) )

: amin((t+M)/2, t, M);


umbral = 1 ; // determina lo exhaustivo de la búsqueda del ángulo mínimo

interseccion=.1 ;

exceso = [1,1,1] * (sobrado ? interseccion : 0);


Xd = acota(inf_x * tarugo[0], 0, tarugo[0]);

Zd = acota(inf_z * tarugo[2], 0, tarugo[2]);


Xi = tarugo[0] - Xd;

Zi = tarugo[2] - Zd ;


if ( Xd>0 && Zd>0 && Xi>0 && Zi>0 || Xd+Zd==0 || Xi+Zi==0 ) {

T = tan(90 - acota(suavidad, 0, 1) * (90-amin()));

Ad = sqrt(Ad2(T));

Ai = sqrt(Ai2(T));

Bd = Zd / (1-sqrt(1-pow((Xd>Ad?Ad:Xd)/Ad,2)));

Bi = Zi / (1-sqrt(1-pow((Xi>Ai?Ai:Xi)/Ai,2)));

translate( center ? -tarugo/2 : [0,0,0] )

union() {

intersection() {

translate(-exceso/2)

cube([Xd+exceso[0]/2, tarugo[1]+exceso[1], tarugo[2]+exceso[2]]);

translate([0,-(interseccion+exceso[1])/2,tarugo[2]-Bd])

rotate([-90,0,0])

scale([1,Bd/Ad,1])

cylinder(r=Ad, h=tarugo[1]+exceso[1]+interseccion);

}

difference() {

translate(-exceso/2)

cube([tarugo[0], tarugo[1], Zi]+exceso);

translate([tarugo[0],-(interseccion+exceso[1])/2,Bi])

rotate([-90,0,0])

scale([1,Bi/Ai,1])

cylinder(r=Ai, h=tarugo[1]+exceso[1]+interseccion);

}

}

}

else

echo("ladera incongruente!");

}