2D 轉圓柱


在我的作品中有 Zentangle bracelet 以及 Generalized zentangle bracelet

Zentangle bracelet

Generalized zentangle bracelet

這兩個作品上的花紋,其實是使用 Symmetrical triangle generator 產生的:

Symmetrical triangle generator

要怎麼完成這樣的作品?手環是圓柱形,而原本的花紋設計是 2D 平面,基本上,是可以設計直接將線條畫在圓柱上,例如 Random maze cylinder generator 就是這麼做的,不過,Random maze cylinder generator 只需要垂直與水平兩種線就可以完成,這樣的設計方式還算不複雜,然而,上面的情況,線條必須有各個方向,雖然是可以設計的出來,不過數學與實作上都會複雜一些。

圓的組成

先別管上面的作品好了,我們來朝另一個方向想,先來看看,怎麼將字畫在圓柱上?最簡單的方式,就是將字投影到圓柱上。例如:

radius = 10;
height = 20;
thickness = 1;
$fn = 24;

render() intersection() {
    difference() {
        cylinder(
            r1 = radius + thickness, 
            r2 = radius + thickness, 
            h = height, center = true);
        cylinder(
            r1 = radius, 
            r2 = radius, 
            h = height, center = true);
    }

    rotate([90, 0, 0]) linear_extrude(radius + thickness) 
        text("A", size = height, valign = "center", halign = "center");
}

這是一種方法沒錯,完成的結果如下:

2D 轉圓柱

不過,這個方式沒辦法完成以下的效果:

2D 轉圓柱

因為用投影的方式,文字只能投到半圓柱上,沒辦法做出跨 180 度以上的字繞圓柱效果。

回想一下 圓的組成 中談到的,圓實際上可以看成是由數個三角形圍繞而成,那麼圓柱呢?可以看成是數個三角形拉高後圍繞組成,如果將這些三角形攤平,並與 2D 圖案拉高後取交集:

2D 轉圓柱

這就會取得許多圓弧(其實是梯形),接著將這些圓弧繞起來成為一個圓:

2D 轉圓柱

那麼就又可以得到一個圓柱了,而圓柱上就會有原本 2D 平面的圖案,這樣是不是比要使用 3D 座標簡單呢!

程式的實作

在實作時,首先要決定的是,2D 圖案的範圍,也就是長與寬,為了方便,圖案定位方式如下:

2D 轉圓柱

因此,如果要將文字 A 繞成圓柱,我們把字這樣放:

linear_extrude(thickness) 
    translate([font_size / 2, font_size / 2, 0])
        rotate(90) 
            text("A", size = font_size, valign = "center", halign = "center");

2D 轉圓柱

接著,我們必須有個模組,建立組成圓時必須使用的三角形,每個三角形是等腰三角形,兩個等腰的長其實就是圓半徑。

OpenSCAD 中,polygon 模組可以指定座標來建立多邊形,因此可以使用 polygon 模組來建立三角形;如果圓由 fn 個三角形組成,那麼兩個等腰的夾角就是 a = 360 / fn,若兩個等腰形成的頂點為原點,那麼另兩個頂點的座標就是 [radius * cos(a / 2), radius * sin(a / 2)][radius * cos(a / 2), -radius * sin(a / 2)],因此,我們建立以下的 one_over_fn_for_circle 模組:

radius = 10;
$fn = 24;

module one_over_fn_for_circle(radius, fn) {
    a = 360 / fn;
    x = radius * cos(a / 2);
    y = radius * sin(a / 2);
    polygon(points=[[0, 0], [x, y],[x, -y]]);
}

one_over_fn_for_circle(radius, $fn);

2D 轉圓柱

接著,我們必須將指定的文字立起來:

2D 轉圓柱

然後用攤開的三角形拉高去取交集…

2D 轉圓柱

那麼,被攤開的圓,圓周長必須等於 2D 圖案的長 length,因此,2 * PI * radius = length,被攤開的圓,半徑就是 radius = length / (2 * PI),接著就只要跑迴圈,建立每個三角形,移至正確的位置,與文字取交集,再移回一開始建立三角形時的位置,轉個適當角度,將可以組合回原來的圓,因此,如果只是針對文字轉 2D 圖案,我們可以這麼實作:

tx = "A";
font_size = 20;
thickness = 1;
$fn = 24;

module one_over_fn_for_circle(radius, fn) {
    a = 360 / fn;
    x = radius * cos(a / 2);
    y = radius * sin(a / 2);
    polygon(points=[[0, 0], [x, y],[x, -y]]);
}

module text_to_cylinder(tx, font_size, thickness, fn) {
    r = font_size / 6.28318;
    a = 360 / fn;
    y = r * sin(a / 2);
    for(i = [0 : fn - 1]) {
        // 將交集結果移動、轉回圓的一部份
        rotate(a * i) translate([0, -(2 * y * i + y), 0])

        // 取交集
        intersection() {
            // 將三角形攤開
            translate([0, 2 * y * i + y, 0]) 
                linear_extrude(font_size) 
                    one_over_fn_for_circle(r, fn);

            // 文字立起
            translate([r - thickness, 0, font_size]) 
              rotate([0, 90, 0]) 
                    // 文字
                    linear_extrude(thickness)
                        translate([font_size / 2, font_size / 2, 0])
                             rotate(90) 
                                 text(tx, size = font_size, valign = "center", halign = "center");
        }
    }
} 

text_to_cylinder(tx, font_size, thickness, $fn); 

這樣就可以得到以下的結果了:

2D 轉圓柱

從這張圖可以看到,圖案如果有較陡的斜線會有明顯的鋸齒,這可以加大 $fn 來改善,加大 $fn 就表示組成圓的三角形越多,而與圖案的交集就越多,就會需要比較長的運算時間,這是這個方法的缺點。

實際上,這個演算可適用於各種四邊形的 2D 圖案,而在 操作 children() 中看過,可以使用 children 來代表要操作的對象,因此,可以將上例修改為更通用,例如:

tx = "A";
font_size = 20;
thickness = 1;
fn = 24;

module one_over_fn_for_circle(radius, fn) {
    a = 360 / fn;
    x = radius * cos(a / 2);
    y = radius * sin(a / 2);
    polygon(points=[[0, 0], [x, y],[x, -y]]);
}

module square_to_cylinder(length, width, square_thickness, fn) {
    r = length / 6.28318;
    a = 360 / fn;
    y = r * sin(a / 2);
    for(i = [0 : fn - 1]) {
        // 將交集結果移動、轉回圓的一部份
        rotate(a * i) translate([0, -(2 * y * i + y), 0])
        // 取交集
        intersection() {
             // 將三角形攤開
            translate([0, 2 * y * i + y, 0]) 
                linear_extrude(width) 
                    one_over_fn_for_circle(r, fn);
            // 圖案立起
            translate([r - square_thickness, 0, width]) 
                rotate([0, 90, 0]) 
                    children(0);
        }
    }
}

square_to_cylinder(font_size, font_size, thickness, fn)
    linear_extrude(thickness)
        translate([font_size / 2, font_size / 2, 0])
            rotate(90) 
                text(tx, size = font_size, valign = "center", halign = "center");

上面的 square_to_cylinder 模組是通用的版本,只要指定一個 2D 圖案,並告知其長、寬、厚度與圓由幾個三角形組成,就會為建立一個圓柱,表面為指定的圖案了,因此,也可以使用 surface 讀取圖片來建立 2D 圖案,像 PNG to pen holder 就是這樣來的囉!

PNG to pen holder