sweep 與 loft

November 26, 2021

sweep 與 loft 在 3D 模型的建立過程,經常運用的手法,不過,在不同的 CAD 軟體中,sweep 與 loft 的定義略有不同,然而共同的概念是,它們將 3D 實體視為一組 2D 切面(Section)的組成。

sweep

先來看 sweep,如果將一條圓柱體,拿刀子逐層切開,可以得到許多圓切面,反過來就是,如果有許多圓切面,可以組成一個圓柱體。

不過,Workplanesweep 不需要你自行處理切面,你只要給它一條路徑,它會將管理中的 Wire 沿著路徑計算出切面,然後組成一個 3D 實體。例如:

from math import radians, cos, sin
import cadquery as cq

r1 = 100
r2 = 50
fn = 12

# 在 XZ 平面為基礎建立路徑
path = cq.Workplane('XZ').polyline([
      (r1 * cos(radians(a)), r1 * sin(radians(a)))
         for a in range(0, 90, 10)
])

arc3d = cq.Workplane().center(r1, 0).polygon(fn, r2).sweep(path)

在建立 Workplane 時,可以指定使用哪個平面為基礎,在上例中指定了 XZ 平面,在上面建立了一個圓弧,執行 sweep 時,將形狀置於路徑的起始處,然後就像該形狀延著路徑走建立了 3D 實體:

sweep 與 loft

這感覺就像是實作了一個擠出,確實地,Workplanesweep 比較像是路徑擠出的概念,只不過若做點適當的封裝,會更能看出程式碼的意圖,例如,實作一個旋轉擠出函式 rotate_extrude(模仿 OpenSCAD 中的 rotate_extrude):

from math import radians, cos, sin
import cadquery as cq

r1 = 100
r2 = 50
fn = 12

def rotate_extrude(shape, radius, a):
      path = cq.Workplane('XZ').center(-radius, 0).polyline([
         (radius * cos(radians(a)), radius * sin(radians(a)))
            for a in range(0, a + 1)
      ])

      return (shape.sweep(path)
                  .rotate((0, 0, 0), (1, 0, 0), -90)
                  .translate((radius, 0, 0)))

circle = cq.Workplane().polygon(fn, r2)

arc3d = rotate_extrude(circle, r1, 180)

show_object(arc3d)

rotate_extrude 接受一個在 XY 平面的 Workplane,使用其中的 Wire,可指定旋轉擠出時的半徑與角度,來看看以下的效果:

sweep 與 loft

別忘了,如果 Wire 是一個包著另一個,會構成減集的效果,因此,若將上例的 circle 改為:

circle = cq.Workplane().polygon(fn, r2).polygon(fn, r2 / 2)

就會做出一個中空的管子了:

sweep 與 loft

sweep 會沿著路徑計算出切面來,不過,若你想自行建立切面呢?在 CadQuery 中,這可以透過 Workplaneloft 來達到。例如:

import cadquery as cq

trapezoid3d = (cq.Workplane().rect(10, 10)         # 第一個切面
                 .workplane(offset = 10).rect(5, 5) # 第二個切面
                 .loft())

workplane 方法在建立新的 2D 平面時,可以用 offset 指定偏移量(origin 可指定新平面中心),loft 會使用管理中的切面建立模型:

sweep 與 loft

loft

loft 的切面彼此間可以是不同的形狀。例如:

import cadquery as cq
                  
lofted = (cq.Workplane().circle(10)
            .workplane(offset = 10).rect(5, 5)
            .loft())

這會構成以下的模型:

sweep 與 loft

有個問題是,切面不見得都是 XY、YZ、XZ 的方向啊!?Workplane 在建立時,其實可以透過 Plane 來指定平面的法向量,例如:

import cadquery as cq
                  
lofted = (cq.Workplane(cq.Plane(origin = (0, 0), normal = (1, 1, 1)))
            .circle(10)
            .workplane(offset = 10).rect(5, 5)
            .loft())

這會建立以下的模型,可以看到模型朝向 (1, 1, 1) 的方向:

sweep 與 loft

如果切面是在不同的平面上,那麼可以透過 add 收集在一個 Workplane,然後透過 toPending 加入至待處理(pending)區,之後進行 loft

import cadquery as cq        

circle = cq.Workplane().circle(10)
rect = cq.Workplane(
            cq.Plane(origin = (0, 0, 10), normal = (1, 1, 1))
         ).rect(5, 5)                

lofted = (cq.Workplane()
            .add(circle)  # 收集圓切面
            .add(rect)    # 收集方切面
            .toPending()
            .loft())

這會建立以下的模型:

sweep 與 loft

待處理區中可能會有邊、線等,Workplane 有些方法,會將物件放入待處理區,例如 lineTopolyline 等,而有些方法,例如 closeextrudesweeploft 等,其實是從待處理區中取得物件進行處理,API 文件基本上,會提到它是從 objects 或者是待處理區中取得資料,若有疑問,可以查詢一下。

在〈堆疊與選擇器〉的最後有談到官方文件〈CadQuery Concepts〉,其中的〈An Introspective Example〉,透過範例來說明 Workplane 上一些方法對 objects、待處理區的作用,若有時間可以看一下,搭配 API 文件,在一些進階需求時,實作上就會更有彈性。