曲線與曲面

November 26, 2021

Workplane 提供了相關方法,可以基於參數方程式等來描述曲線與曲面。

spline

spline 方法,spline 字面意義是樣條,基於一組控制點,透過內插的方式來建立曲線,不同的樣條曲線計算會有不同的效果,Workplanespline 會建立通過控制點的曲線,例如,來個螺旋曲線:

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

radius = 10
num_of_pts = 30
h_step = 1
fn = 6

a_step = 360 / fn
points = [
      (
            radius * cos(radians(i * a_step)),  # x
            radius * sin(radians(i * a_step)),  # y
            i * h_step                          # z
      ) for i in range(num_of_pts)
]

helix = cq.Workplane().spline(points)
show_object(helix)

# 標示控制點
box = cq.Workplane().box(1, 1, 1) 
for p in points:
      show_object(box.translate(p))

這會顯示以下的模型,可以看到控制點與控制點間也是曲線:

曲線與曲面

spline 可以接受的點可以是三維,不過,線沒有寬度、厚度的概念,因此 spline 被歸類在 2D 操作,事實上,先前談過的 polyline 也可以接受三維的點,只不過,像 splinepolyline 這類方法,主要是會在一個平面(例如 XY 平面)建立曲線,後續再進行進一步操作,像是 extrude

import cadquery as cq

result = (cq.Workplane()
            .spline([(0, 0), (10, 10), (0, 10)]).close()
            .extrude(1)
         )

這會建立以下的模型:

曲線與曲面

parametricCurve

如果想要以參數式的形式來畫曲線,可以使用 parametricCurve 方法,例如:

from math import sin, pi
import cadquery as cq

def sin_wave(t):
      return (t * 2 * pi, sin(t * 2 * pi))

result = (cq.Workplane()
            .moveTo(2 * pi, 0)
            .lineTo(2 * pi, 5)
            .lineTo(0, 5)
            .lineTo(0, 0)
            .parametricCurve(sin_wave)
            .wire()
            .extrude(1)
         )

parametricCurve 指定的函式可以接受一個參數,傳入的值預設會是 0 到 1,分別可以透過 startstop 來指定(傳入的值包含兩者),預設會切為 400 等分,可以透過 N 來指定,因此函式會接受到 N + 1 次的值,函式要傳回座標點,可以是 2D 或 3D,傳回的座標點會作為控制點,計算內插值構成曲線,曲線會通過控制點:

曲線與曲面

你可以試著在以下的範例指定 N 為 2、3 或更大的值,看看控制點與建立的曲線:

from math import sin, pi
import cadquery as cq

def sin_wave(t):
      return (t * 2 * pi, sin(t * 2 * pi))

N = 3

result = (cq.Workplane()
            .parametricCurve(sin_wave, N = N)
         )
show_object(result)
            
box = cq.Workplane().box(.1, .1, .1)
step = 1 / N
for i in range(N + 1):
      show_object(box.translate(sin_wave(i * step)))

N 為 3 時會顯示以下的結果:

曲線與曲面

(CadQuery API 文件沒有明確指出曲線是採用哪種內插計算,不過,應該是〈Catmull-Rom 樣條〉這類曲線。)

parametricSurface

Workplane 有個 parametricSurface,使用上類似 parametricCurve,不過被指定的函式接受兩個值,傳回的座標會用來建立曲面:

from math import cos, pi
import cadquery as cq

def wave(u, v):
      return (u, v, 0.5 * cos(pi * u) * cos(pi * v))

w = cq.Workplane().parametricSurface(wave, N = 5, stop = 3)

曲線與曲面

曲面沒有厚度,因此還是被歸為 2D 的操作,這個曲面的應用是切割實體。例如:

from math import cos, pi
import cadquery as cq

def wave(u, v):
      return (u, v, 0.5 * cos(pi * u) * cos(pi * v))

w = (cq.Workplane()
       .center(1.5, 1.5)
       .box(3, 3, 3)
       .split(cq.Workplane().parametricSurface(wave, N = 5, stop = 3))
       .solids('<Z')
    )

split 可以指定一個形狀,它會用來切割目前既有的實體,在上例中,建立的曲面用來切割方塊,然後取 Z 軸負方向最下面的實體,這樣就得到一個有厚度,這種下面平整的厚度實體稱為 block:

曲線與曲面

如果想直接按照曲面來建立厚度呢?就目前而言,這需要一些技巧,可以先用 shell 造出一層殼:

from math import cos, pi
import cadquery as cq

def wave(u, v):
      return (u, v, 0.5 * cos(pi * u) * cos(pi * v))

box = cq.Workplane().box(3, 3, 3)
w = (box.translate((1.5, 1.5))
        .split(cq.Workplane().parametricSurface(wave, N = 5, stop = 3))
        .solids('<Z')
        .faces('>X or <X or <Y or <Z')
        .shell(.1)
    )
      
show_object(w)

在〈殼的建立〉中談過,可以將不需要建殼的面選取,然後再執行 shell,不過至少要留下兩個連續的面:

曲線與曲面

為了取得曲面部份,可以用個方塊來交集:

from math import cos, pi
import cadquery as cq

def wave(u, v):
      return (u, v, 0.5 * cos(pi * u) * cos(pi * v))

box = cq.Workplane().box(3, 3, 3)
w = (box.translate((1.5, 1.5))
        .split(cq.Workplane().parametricSurface(wave, N = 5, stop = 3))
        .solids('<Z')
        .faces('>X or <X or <Y or <Z')
        .shell(.1)
    ) & box.translate((1.5, 1.5))
      
show_object(w)

這就得到了以下的結果:

曲線與曲面

interpPlate

Workplane 有個 interpPlate 方法,可以建立 N 邊曲面(N-Sided Surface),使用時指定邊的頂點以及面的控制點,也可以指定厚度,例如:

import cadquery as cq

thickness = .1
edge_points = [(0, 0), (10, 0), (10, 10), (0, 10)]
surface_points = [(2.5, 2.5, 2.5), (7.5, 7.5, -2.5)]

plate = cq.Workplane().interpPlate(edge_points, surface_points, thickness)

show_object(plate)

box = cq.Workplane().box(.5, .5, .5)
for p in surface_points:
      show_object(box.translate(p))

點一次加入太多的話,不容易控制,使用時大概是逐點加入,看看可否控制出想要的曲面,上例會顯示以下的結果:

曲線與曲面