Tangentes a un círculo con OpenSCAD: trabajar con sucedáneos de curvas

He diseñando una pieza que básicamente es un cilindro unido a un cubo con la transformación hull() de OpenSCAD. Si volteo la pieza sobre su lado izquierdo la podré fabricar sin necesidad de soportes, pero desconozco el ángulo de giro.

El siguiente gráfico representa el problema:


radio = 25;
ancho = 15;
alto = 10;
x = 15;
y = 40;

rotate([0,0,¿cuánto?])
hull(){
    #circle(radio);
    #translate([x, y])
        square([ancho, alto]);
}

 planteamiento original

Generalizando, se puede calcular la tangente a dos círculos conocidos. En el caso particular de que uno de los círculos tenga radio=0 tendremos resuelto el problema anterior: tangente a un círculo que pasa por un punto.

planteamiento generalizado


El segmento H que une los dos círculos mide raiz(P2+Q2) y forma con la horizontal un ángulo arctan(Q/P). A su vez es la bisectriz del ángulo formado por las tangentes T y T'. El ángulo entre H y T será entonces arcsen( (R-r) / H ).

// planteamiento: calcular los ángulos de las tangentes a dos círculos

// parámetros
R = 25 ;
r = 12 ;
p = 35 ;
q = 40 ;

// cálculo de los ángulos de tangente (para p<0 sumamos 180º)
alfa = atan(q/p) + asin((R-r)/sqrt(pow(p, 2)+pow(q, 2))) + (p<0?180:0);
beta = atan(q/p) - asin((R-r)/sqrt(pow(p, 2)+pow(q, 2))) + (p<0?180:0);

//---------------
circle(R);
translate([p, q])
    circle(r);
//---------------
    
// trazar una tangente calculando el punto de contacto, que depende de alfa
color("red") 
translate([R*cos(alfa-90), R*sin(alfa-90)])
rotate([0,0,alfa])
            raya();
// el punto de contacto de la otra tangente depende de beta
color("blue")
translate([R*cos(beta+90), R*sin(beta+90)])
rotate([0,0,beta])
            raya();

module raya() {
   translate([-15, -.25])
      square([75, .50]);
}
 tangentes ideales


Ahora llega la cruda realidad: OpenSCAD no trabaja con curvas, sino que las simula más o menos bien con rectas y planos (según trabajemos en 2 o 3D). Es posible que la tangente a nuestro círculo imaginario no llegue a tocar el polígono correspondiente. Cuando procesamos nuestro diseño con un fileteador como Slic3r cabe la posibilidad de que la pieza no sea bien interpretada y el resultado final tenga problemas. Gráficamente nos encontramos con esto:


...

//---------------
$fn=8;
circle(R);
translate([p, q])
    circle(r);
//---------------

...

 tangentes cruelmente reales

Los lados del polígono no están puestos de cualquier manera: OpenSCAD empieza a dibujar a partir del punto de coordenadas (radio, 0) en sentido antihorario. Conocemos el ángulo de la tangente, así que si giramos el polígono angulo-90º se estará empezando a dibujar por el punto de tangencia. El siguiente código sería para apañar la tangente roja, pero entonces dejamos la azul peor:

...

//---------------
$fn=8;
rotate([0,0,alfa-90]);
    circle(R);
translate([p, q])
    rotate([0,0,alfa-90]);
       circle(r);
//---------------
    
...

 arreglo de la tangente roja

Si necesitamos las dos tangentes correctamente unidas la solución puede ser hacer una pareja de círculos rotados para la tangente roja, fundidos con hull() con otra pareja girada para la tangente azul:

...

//---------------
$fn=8;
hull() {
    rotate([0,0,alfa-90])
        circle(R);
    rotate([0,0,beta-90])
        circle(R);
}
translate([p, q])
    hull() {
        rotate([0,0,alfa-90])
            circle(r);
        rotate([0,0,beta-90])
            circle(r);
    }
//---------------
    
...

 solución definitiva

Aunque sólo sea por cerrar el círculo, falta resolver el planteamiento inicial. Consideramos que el circulo desplazado tiene radio=0, y está en la esquina superior izquierda del rectángulo:

radio = 25;
ancho = 15;
alto = 10;
x = 15;
y = 40;

beta = atan((y+alto)/x)
   - asin(radio/sqrt(pow(x, 2)+pow(y+alto, 2)));

rotate([0,0,180-beta])
    hull() {
        rotate([0, 0, beta+90])
            #circle(radio);
        #translate([x, y])
            square([ancho, alto]);
    }

     solución al planteamiento original
La rotación resaltada en negrita garantiza que la línea de apoyo va a ser matemáticamente horizontal. Con un valor de $fn=6 se puede ver que esa rotación es necesaria. De todas formas, si el círculo se ve cabalmente redondo no hace falta dicha rotación.