Tangente a una elipse con OpenSCAD

Cuando trabajamos con superficies planas y curvas en OpenSCAD es interesante evitar los escalones. En el caso más simple mezclaremos cilindros con planos ortogonales, y calcular las coordenadas de corte es fácil conociendo el radio del cilindro. Podemos encontrar problemas si el número de facetas del cilindro es tal que no coincide una arista con el plano al que hay que unirlo. Eso se soluciona fácilmente haciendo que la cantidad de caras sea múltiplo de 4.


Si el plano no es ortogonal respecto del cilindro podemos calcular el punto de tangencia con algo de trigonometría elemental. Pero cuando el cilindro está deformado y tiene base elíptica tendremos más complicaciones.
En el gráfico adjunto tenemos una elipse que hemos hecho en OpenSCAD mediante un cilindro de radio a con un escalado de b/a en el eje Y. De momento, queremos saber el punto de tangencia de una recta que forma un ángulo v con la vertical. Para ello partiremos de la fórmula de la elipse que relaciona cada uno de sus puntos con sus semiejes:
  x2/a2 + y2/b2 = 1
despejando y:
  y = ± b*raiz(1-x2/a2) [1]
tomamos el valor positivo de y porque en este caso no nos sirve el negativo, y la derivada será:
  y' = - bx/(a2 * raiz(1 - x2 / a2) ) [2]
como la derivada de una función en un punto es su tangente, tenemos que
  y' = tan(v-90) = -1/tan(v) [3]
a partir de [2] y [3] despejamos x:
  x=raiz(1/((b*tan(v)/a2)2 + 1/a2))
y terminamos resolviendo [1] una vez conocido x para calcular y.

Puedes probarlo con este código de OpenSCAD:

/* planteamiento:
   - sea una elipse proyectada en el eje Z, con semiejes semieje_a y semieje_b y altura alto
     y un tarugo de ancho x largo y altura alto
   - se trata de poner el tarugo de forma que su lado izquierdo toque a la elipse 
     con un angulo dado, para lo que necesitamos las coordenadas de tangencia
*/

// suavizar las superficies curvas
$fs= .1 ; 
$fa= .1 ; 

alto = 10 ;

ancho = 45 ;
largo = 40 ;
angulo = 62 ;

semieje_a = 10 ;
semieje_b =18 ; 

color("red") scale([1, semieje_b/semieje_a, 1])
   cylinder(r=semieje_a, h=alto, center=true);

// calcular X
X = sqrt(1/(pow(semieje_b*tan(angulo)/pow(semieje_a,2),2)+1/pow(semieje_a,2)));

// trasladar al punto de contacto
translate([X, semieje_b*sqrt(1-pow(X/semieje_a,2)), 0])
   // rotar el angulo de tangencia
   rotate([0, 0, angulo]) 
      // colocar en [0, 0, 0] el punto que debe tocar la elipse
      translate([ancho/2, 0, 0]) 
         // tarugo de partida
         cube([ancho, largo, alto], center=true);

También podemos encontrarnos con el problema opuesto: tenemos un plano inclinado en un ángulo v y queremos que se fusione con un plano vertical mediante una superficie elíptica. Podemos fijar a y  como parámetros para modelar el aspecto de la unión, y hallar el b necesario.


Con las expresiones [2] y [3] podemos despejar b:
  b = a2 * raiz(1 - x2/a2) / (x * tan(v)) [4]
Sustituyendo en [1] este valor de b y simplificando:
  y = (a2 - x2) / (x * tan(v)) [5]
Viendo el gráfico, por trigonometría elemental podemos conlcuir que:
  tan(v) = (a - x) / (y - h)
Si despejamos y podemos igualar con [5]:
  y = (a - x) / tan(v) + h = (a2 - x2) / (x * tan(v))
Sólo queda despejar x como función de los parámetros a, h y v
  x = a2 / (a + h * tan(v))
Conocido x podemos calcular b con [4], y finalmente y con [1]

El siguiente ejemplo es más complejo, porque no se limita a dibujar un óvalo tangente a una recta, sino que ya hace el recorte, restando a un tarugo el óvalo de redondeo, y el resultado se resta al tarugo inicial:

/* planteamiento:
   - sea un tarugo de ancho * largo, con un un corte que forma con la vertical un angulo preestablecido
     si lo vemos desde arriba (Ctrl+4 con OpenSCAD) veremos que el lado izquierdo del tarugo mide "largo",
     y el lado izquierdo mide "h" (a calcular en funcion del angulo)
   - queremos suavizar el angulo derecho con un ovalo de anchura arbitraria y con un grado de estiramiento 
     tal que sea tangente al corte citado

*/

// suavizar las superficies curvas
$fs= .1 ; 
$fa= .1 ; 

alto = 10 ;

ancho = 45 ;
largo = 40 ;
angulo = 62 ;
   
a = ancho/3 ; // semieje horizontal del ovalo de redondeo (este valor es arbitrario)
h = largo-ancho/tan(angulo); // este valor es conocido o facilmente calculable

// calculos: X e Y son las coordenadas de contacto, y B el semieje vertical del ovalo
X = pow(a,2)/(a+h*tan(angulo));
B = pow(a,2)*sqrt(1-pow(X/a,2)) / (X * tan(angulo));
Y = B * sqrt(1 - pow(X/a, 2));

difference() {
   translate([0, largo/2, 0])
      cube([ancho, largo, alto], center = true);
   translate([-ancho/2, largo,0])
      rotate([0,0,angulo-90])
         translate([100/2, 100/2, 0])
            cube(100, center = true);
   x_origen_ovalo=ancho/2-a;
   difference() {
      translate([x_origen_ovalo + X + 100/2,Y-100/2,0])
         cube(100, center = true);
#         translate([x_origen_ovalo,0,0]) 
         scale([1,B/a,1.01]) cylinder(r=a, h=alto, center=true);
   }
}