hull 折線
November 27, 2021線本身是沒有寬度的,如果只是要在螢幕上畫線,這不成問題,只要看得出是一條線就可以了;然而,如果要 3D 列印的話,線就得有寬度,或者嚴格的說法是,圍著線建立起多邊形。
要圍著線建立起多邊形,其實是個稍微複雜點的問題,因為得處理轉角的問題,例如在我的 dotSCAD 程式庫,就實現了 polyline2d
函式,其中可以看到有三種轉角可以設定:
有興趣的話,你可以試著挑戰看看實現這種折線,然而就 3D 建模而言,大部份的情況下,並不會在意轉角的樣式,單純就只是要有個具寬度的線罷了,這時可以用另一種簡單的方式,也就是〈建立 Convex hull〉中談到的,在點與點間設定一個形狀,然後計算它們的凸包構成一條直線,將每個直線聯集後,就可以得到一條折線。
2D 折線
以下是個簡單的實作,polylineJoin2D
的 join
接受一個內含 Wire
的 Workplane
,用來指定折點:
from scipy.spatial import ConvexHull
from cadquery import Workplane
# 用 Wire 來建立凸包
def hull2D(points):
hull = ConvexHull(points)
return (cq.Workplane()
.polyline([points[i] for i in hull.vertices])
.close()
.val()
)
# 將 Workplane 中的 Wire 頂點轉為 (x, y)
def toPoints(workplane):
return [(v.X, v.Y) for v in workplane.vertices().vals()]
# 指定折點的 Wire 來建立折線
def polylineJoin2D(points, join):
# 在每個點放上一個 join
joins = [join.translate(p) for p in points]
# 建立點與點間的凸包
lines = [
hull2D(toPoints(joins[i]) + toPoints(joins[i + 1]))
for i in range(len(joins) - 1)
]
# 將全部的凸包擠出,目的是為了利用 `union` 方法來交集
polyline = Workplane(lines[0]).toPending().extrude(1)
for i in range(1, len(lines)):
polyline = polyline.union(Workplane(lines[i]).toPending().extrude(1))
# 取 XY 平面上的 Wire 就可以了
return polyline.faces('<Z').wires().val()
points = [(0, 0), (10, 10), (0, 15), (-10, 10), (-10, 0)]
polyline = polylineJoin2D(points, Workplane().polygon(6, 1))
show_object(polyline)
這會建立以下的結果:
3D 折線
這個做法可以推廣,建立 3D 版本的折線:
from scipy.spatial import ConvexHull
from cadquery import Vector, Edge, Wire, Solid, Shell, Face, Workplane
# 〈實作 polyhedron〉的 polyhedron 函式
def polyhedron(points, faces):
def _edges(vectors, face_indices):
leng_vertices = len(face_indices)
return (
Edge.makeLine(
vectors[face_indices[i]],
vectors[face_indices[(i + 1) % leng_vertices]]
)
for i in range(leng_vertices)
)
vectors = [Vector(*p) for p in points]
return Solid.makeSolid(
Shell.makeShell(
Face.makeFromWires(
Wire.assembleEdges(
_edges(vectors, face_indices)
)
)
for face_indices in faces
)
)
# 將 Workplane 中的 Solid 頂點轉為 (x, y, z)
def toPoints(workplane):
return [(v.X, v.Y, v.Z) for v in workplane.vertices().vals()]
# 建立 3D 版本的凸包
def hull3D(points):
hull = ConvexHull(points)
# 凸包上的頂點
vertices = [points[i] for i in hull.vertices]
# 用來查詢頂點的索引值
v_i_lookup = {v: i for i, v in enumerate(vertices)}
# 建立面索引
faces = [
[v_i_lookup[points[i]] for i in face]
for face in hull.simplices
]
return polyhedron(vertices, faces)
def polylineJoin3D(points, join):
# 在每個點放上一個 join
joins = [join.translate(p) for p in points]
# 建立點與點間的凸包
lines = [
hull3D(toPoints(joins[i]) + toPoints(joins[i + 1]))
for i in range(len(joins) - 1)
]
# 將全部的凸包交集
polyline = Workplane(lines[0])
for i in range(1, len(lines)):
polyline = polyline.union(Workplane(lines[i]))
return polyline
points = [(0, 0, 0), (10, 0, 0), (10, 0, 10), (10, 10, 10)]
polyline = polylineJoin3D(points, Workplane().box(1, 1, 1))
show_object(polyline)
這會建立以下的結果: