/// Definitions of standard holes that could be drilled or cut into solids.
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
// Tops
holeTypeSimple = 0
holeTypeCounterbore = 1
holeTypeCountersink = 2
/// A hole top with no decoration.
@(feature_tree = false)
export fn simple() {
return { feature = holeTypeSimple }
}
/// Cut a straight vertical counterbore at the top of the hole.
/// Typically used when a fastener (e.g. the head cap on a screw)
/// needs to sit flush with the solid's surface.
/// ```kcl,legacySketch
/// // Model a cube
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
/// |> translate(x = 5)
///
/// // Add a hole to the cube.
/// // It'll have a drilled end, and a counterbore (vertical hole that emerges from a larger hole)
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 5],
/// holeBottom = hole::drill(pointAngle = 110deg),
/// holeBody = hole::blind(depth = 5, diameter = 8),
/// holeType = hole::counterbore(diameter = 12, depth = 3.5),
/// )
/// ```
@(feature_tree = false)
export fn counterbore(diameter: number(Length), depth: number(Length)) {
return {
diameter = diameter,
depth = depth,
feature = holeTypeCounterbore
}
}
/// Cut an angled countersink at the top of the hole.
/// Typically used when a conical screw head has to sit flush
/// with the surface being cut into.
/// ```kcl,legacySketch
/// // Model a cube
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
/// |> translate(x = 5)
///
/// // Add a hole to the cube.
/// // It'll have a drilled end, and a countersink (angled tip at the start).
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 5],
/// holeBottom = hole::drill(pointAngle = 110deg),
/// holeBody = hole::blind(depth = 5, diameter = 8),
/// holeType = hole::countersink(diameter = 14, angle = 100deg),
/// )
/// ```
@(feature_tree = false)
export fn countersink(diameter: number(Length), angle: number(Angle)) {
return {
diameter = diameter,
angle = angle,
feature = holeTypeCountersink
}
}
// Bodies
/// The hole has the given blind depth.
@(feature_tree = false)
export fn blind(depth: number(Length), diameter: number(Length)) {
return {
blindDepth = depth,
diameter = diameter
}
}
// Ends
/// End the hole in an angle, like the end of a drill.
/// ```kcl,legacySketch
/// // Sketch a cube, so we have something to drill into.
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
///
/// // Add a hole with a very pointy drilled bottom.
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 0],
/// holeBottom = hole::drill(pointAngle = 25deg),
/// holeBody = hole::blind(depth = 1, diameter = 8),
/// holeType = hole::simple(),
/// )
/// ```
@(feature_tree = false)
export fn drill(pointAngle: number(Angle)) {
return { drillBitAngle = pointAngle }
}
/// End the hole flat.
/// ```kcl,legacySketch
/// // Sketch a cube, so we have something to drill into.
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
///
/// // Add a hole with a flat bottom.
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 0],
/// holeBottom = hole::flat(),
/// holeBody = hole::blind(depth = 2, diameter = 8),
/// holeType = hole::simple(),
/// )
/// ```
@(feature_tree = false)
export fn flat() {
return { drillBitAngle = 180deg }
}
// Defining the hole
@(feature_tree = false)
fn drawTop(@partialHoleProfile, body, holeType) {
prof = if holeType.feature == holeTypeSimple {
partialHoleProfile
|> xLine(length = body.diameter / 2)
} else if holeType.feature == holeTypeCounterbore {
lip = (holeType.diameter - body.diameter) / 2
partialHoleProfile
|> yLine(length = holeType.depth)
|> xLine(length = holeType.diameter / 2)
|> yLine(length = -holeType.depth)
|> xLine(length = -lip)
} else if holeType.feature == holeTypeCountersink {
theta = holeType.angle / 2
alpha = 90 - theta
tr = holeType.diameter / 2
br = body.diameter / 2
assert(tr, isGreaterThan = br, error = "Diameter of the countersink must be greater than diameter of the hole's body")
// Ugly trigonometry that I calculated with pen and paper
up = (tr - br) / tan(theta)
b = br / tan(theta)
diag = up/cos(theta)
partialHoleProfile
|> yLine(length = up)
|> xLine(length = tr)
|> angledLine(length = diag, angle = 180 + alpha)
} else {
assert(holeType.feature, isLessThanOrEqual = holeTypeCountersink, error = "unknown holeType feature")
}
return prof
}
@(feature_tree = false)
fn holeTypeHeight(body, holeType) {
height = if holeType.feature == holeTypeSimple {
0
} else if holeType.feature == holeTypeCounterbore {
holeType.depth
} else if holeType.feature == holeTypeCountersink {
t = holeType.angle / 2
tr = holeType.diameter / 2
br = body.diameter / 2
// Ugly trigonometry that I calculated with pen and paper
up = (tr - br) / tan(t)
up
} else {
assert(holeType.feature, isLessThanOrEqual = holeTypeCountersink, error = "unknown holeType feature")
}
return height
}
// Actually defining a hole
/// Profile for the end of the hole. Only really intended for advanced uses.
/// You can use this and `revolveHole` to do tricky custom hole cutting.
@(feature_tree = false)
fn sketchHoleProfile(@sketchOn, holeBottom, holeBody, holeType, shift) {
body = holeBody
bottom = holeBottom
// Calculate the drill bit height
radius = body.diameter / 2
// if drill bit angle is exactly 180 degrees, it causes a divide by zero
// (because tan(theta = 90) = 0).
// So, if it would be 180deg, change it to be 179.99deg.
theta = if abs(bottom.drillBitAngle - 180deg) < 0.01deg {
179.99deg / 2
} else {
bottom.drillBitAngle / 2
}
drillHeight = radius / tan(theta)
// Calculate total model height from holeType to bottom
hBody = body.blindDepth
hBott = drillHeight
totalHeight = hBody + hBott + holeTypeHeight(body, holeType)
sk = startProfile(sketchOn, at = [shift, -totalHeight])
// Drill profile
|> yLine(length = drillHeight)
// Body depth
|> yLine(length = body.blindDepth, tag = $d)
// Top
|> drawTop(body, holeType)
// Body depth
|> yLine(length = -body.blindDepth)
// // Drill profile
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
return { profile = sk, axis = d }
}
/// Given a hole profile, revolve it into a 3D hole solid geometry.
/// This can be subtracted from a solid to form a hole in it.
@(feature_tree = false)
fn revolveHole(@holeProfile, edge) {
return holeProfile
|> revolve(axis = edge)
|> appearance(color = "#ff0000")
}
/// Build a hole's geometry from its top, bottom and depth.
/// Can be subtracted from a solid to cut a hole into it.
@(feature_tree = false)
fn holeGeometry(@solid, face, holeBottom, holeBody, holeType, cutAt) {
intoSolid = startSketchOn(
solid,
normalToFace = face,
alignAxis = -Y,
normalOffset = cutAt[1],
)
toCut = sketchHoleProfile(
intoSolid,
holeBottom,
holeBody,
holeType,
shift = -cutAt[0],
)
drilled = revolveHole(toCut.profile, edge = toCut.axis)
return drilled
}
/// From the hole's parts (bottom, middle, top), cut the hole into the given solid,
/// at the given 2D position on the given face.
/// ```kcl,legacySketch
/// // Model a cube
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
/// |> translate(x = 5)
///
/// // Add a hole to the cube.
/// // It'll have a drilled end, and a countersink (angled tip at the start).
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 5],
/// holeBottom = hole::drill(pointAngle = 110deg),
/// holeBody = hole::blind(depth = 5, diameter = 8),
/// holeType = hole::countersink(diameter = 14, angle = 100deg),
/// )
/// ```
/// ```kcl,legacySketch
/// // Model a cube
/// cubeLen = 20
/// bigCube = startSketchOn(XY)
/// |> startProfile(at = [-cubeLen / 2, -cubeLen / 2 + 10])
/// |> line(end = [cubeLen, 0], tag = $a)
/// |> line(end = [0, cubeLen], tag = $b)
/// |> line(end = [-cubeLen, 0], tag = $c)
/// |> line(end = [0, -cubeLen], tag = $d)
/// |> close()
/// |> extrude(length = cubeLen, symmetric = true)
/// |> translate(x = 5)
///
/// // Add a hole to the cube.
/// // It'll have a drilled end, and a counterbore (vertical hole that emerges from a larger hole)
/// bigCube
/// |> hole::hole(
/// face = a,
/// cutAt = [0, 5],
/// holeBottom = hole::drill(pointAngle = 110deg),
/// holeBody = hole::blind(depth = 5, diameter = 8),
/// holeType = hole::counterbore(diameter = 12, depth = 3.5),
/// )
/// ```
/// ```kcl,sketchSolve
/// blockProfile = sketch(on = XY) {
/// edge1 = line(start = [var 0mm, var 0mm], end = [var 8mm, var 0mm])
/// edge2 = line(start = [var 8mm, var 0mm], end = [var 8mm, var 6mm])
/// edge3 = line(start = [var 8mm, var 6mm], end = [var 0mm, var 6mm])
/// edge4 = line(start = [var 0mm, var 6mm], end = [var 0mm, var 0mm])
/// coincident([edge1.end, edge2.start])
/// coincident([edge2.end, edge3.start])
/// coincident([edge3.end, edge4.start])
/// coincident([edge4.end, edge1.start])
/// horizontal(edge1)
/// vertical(edge2)
/// horizontal(edge3)
/// vertical(edge4)
/// }
///
/// block = extrude(region(point = [4mm, 3mm], sketch = blockProfile), length = 6mm, tagEnd = $top)
/// drilledBlock = hole::hole(
/// block,
/// face = top,
/// cutAt = [4mm, 3mm],
/// holeBottom = hole::flat(),
/// holeBody = hole::blind(depth = 4mm, diameter = 2mm),
/// holeType = hole::simple(),
/// )
/// ```
@(feature_tree = true)
export fn hole(
/// Which solid to add a hole to.
@solid: Solid,
/// Which face of the solid to add the hole to.
/// Controls the orientation of the hole.
face: TaggedFace,
/// Define bottom feature of the hole. E.g. drilled or flat.
holeBottom,
/// Define the main length of the hole. E.g. a blind distance.
holeBody,
/// Define the top feature of the hole. E.g. countersink, counterbore, simple.
holeType,
/// Where to place the cut on the given face of the solid.
/// Given as absolute coordinates in the global scene.
cutAt: [number(Length); 2],
) {
drilled = holeGeometry(solid, face, holeBottom, holeBody, holeType, cutAt)
return subtract(solid, tools = [drilled])
}
/// From the hole's parts (bottom, middle, top), cut the hole into the given solid,
/// at each of the given 2D positions on the given face.
/// Basically like function `hole` but it takes multiple 2D positions in `cutsAt`.
/// ```kcl,legacySketch
/// // Sketch a solid
/// sketch001 = startSketchOn(XY)
/// profile001 = startProfile(sketch001, at = [-3.89, 1.95])
/// |> line(end = [0.63, -3.25])
/// |> xLine(length = 7.15)
/// |> line(end = [0.59, 3.2])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
/// mySolid = extrude(profile001, length = 3, symmetric = true)
///
/// // Add three holes to it.
/// hole001 = hole::holes(
/// mySolid,
/// face = END,
/// cutsAt = [[0, 0], [0, 3], [1, 2]],
/// holeBottom = hole::drill(pointAngle = 110deg),
/// holeBody = hole::blind(depth = 2, diameter = 0.4),
/// holeType = hole::counterbore(diameter = 1, depth = 0.2),
/// )
/// ```
/// ```kcl,sketchSolve
/// blockProfile = sketch(on = XY) {
/// edge1 = line(start = [var 0mm, var 0mm], end = [var 10mm, var 0mm])
/// edge2 = line(start = [var 10mm, var 0mm], end = [var 10mm, var 6mm])
/// edge3 = line(start = [var 10mm, var 6mm], end = [var 0mm, var 6mm])
/// edge4 = line(start = [var 0mm, var 6mm], end = [var 0mm, var 0mm])
/// coincident([edge1.end, edge2.start])
/// coincident([edge2.end, edge3.start])
/// coincident([edge3.end, edge4.start])
/// coincident([edge4.end, edge1.start])
/// horizontal(edge1)
/// vertical(edge2)
/// horizontal(edge3)
/// vertical(edge4)
/// }
///
/// block = extrude(region(point = [5mm, 3mm], sketch = blockProfile), length = 6mm, tagEnd = $top)
/// drilledBlock = hole::holes(
/// block,
/// face = top,
/// cutsAt = [[2mm, 2mm], [5mm, 3mm], [8mm, 4mm]],
/// holeBottom = hole::drill(pointAngle = 90deg),
/// holeBody = hole::blind(depth = 3mm, diameter = 1.5mm),
/// holeType = hole::simple(),
/// )
/// ```
@(feature_tree = true)
export fn holes(
/// Which solid to add a hole to.
@solid: Solid,
/// Which face of the solid to add the hole to.
/// Controls the orientation of the hole.
face: TaggedFace,
/// Define bottom feature of the hole. E.g. drilled or flat.
holeBottom,
/// Define the main length of the hole. E.g. a blind distance.
holeBody,
/// Define the top feature of the hole. E.g. countersink, counterbore, simple.
holeType,
/// Where to place the holes, given as absolute coordinates in the global scene.
cutsAt: [[number(Length); 2]],
) {
drilled = map(cutsAt, f = fn(@cutAt) {
return holeGeometry(solid, face, holeBottom, holeBody, holeType, cutAt)
})
return subtract(solid, tools = drilled)
}
/// Place the given holes in a line.
/// Basically like function `hole` but cuts multiple holes in a line.
/// Works like linear patterns.
@(feature_tree = false)
export fn holesLinear(
/// Which solid to add a hole to.
@solid: Solid,
/// Which face of the solid to add the hole to.
/// Controls the orientation of the hole.
face: TaggedFace,
/// Define bottom feature of the hole. E.g. drilled or flat.
holeBottom,
/// Define the main length of the hole. E.g. a blind distance.
holeBody,
/// Define the top feature of the hole. E.g. countersink, counterbore, simple.
holeType,
/// Where to place the first cut in the linear pattern,
/// given as absolute coordinates in the global scene.
cutAt: [number(Length); 2],
/// How many holes to cut.
instances: number(_),
/// How far between each hole
distance,
/// Along which axis should the holes be cut?
axis: Axis2d | Point2d,
) {
drilled = holeGeometry(solid, face, holeBottom, holeBody, holeType, cutAt)
|> patternLinear3d(instances, distance, axis)
return subtract(solid, tools = drilled)
}
/// Build the 3D shape of the hole which needs to be cut out.
fn holeAtGeometry(@plane: Plane, holeBottom, holeBody, holeType) {
// Create a sketch plane where the cut axis (plane normal) is the sketch plane's -Y axis.
xAxis = [
plane.xAxis[0]: mm,
plane.xAxis[1]: mm,
plane.xAxis[2]: mm
]
yAxis = [
plane.yAxis[0]: mm,
plane.yAxis[1]: mm,
plane.yAxis[2]: mm
]
profilePlane = {
origin = plane.origin,
xAxis = xAxis,
yAxis = vector::cross(yAxis, v = xAxis)
}
profile = sketchHoleProfile(
startSketchOn(profilePlane),
holeBottom,
holeBody,
holeType,
shift = 0,
)
return revolveHole(profile.profile, edge = profile.axis)
}
/// From the hole's parts (bottom, middle, top), cut the hole into the given solid,
/// using the custom plane.
/// The plane's origin determines the hole's center.
/// The plane's X and Y axis determine which way the hole is pointing
/// (it points down the normal i.e. Z axis, following the right-hand rule).
/// This can be used to insert a hole where there is no face, or into a non-planar face.
/// ```kcl,legacySketch
/// // Model a cube.
/// cube1 = startSketchOn(XY)
/// |> rectangle(width = 3, height = 3, center = [0, 0])
/// |> extrude(length = 3)
///
/// // Define a custom plane, into which a hole will be cut.
/// customPlane = {
/// origin = { x = 1.5, y = -1.5, z = 3 },
/// xAxis = { x = -0.5, y = -0.5, z = 0 },
/// yAxis = { x = 0, y = 0.5, z = 0.5 }
/// }
///
/// // Cut the hole through the cube, into the plane.
/// hole::holeAt(
/// [cube1],
/// plane = customPlane,
/// holeBottom = hole::flat(),
/// holeBody = hole::blind(depth = 2, diameter = 1),
/// holeType = hole::counterbore(diameter = 1.4, depth = 1),
/// )
/// ```
/// ```kcl,sketchSolve
/// // Model a cube.
/// cubeProfile = sketch(on = XY) {
/// line1 = line(start = [var -1.5mm, var -1.5mm], end = [var 1.5mm, var -1.5mm])
/// line2 = line(start = [var 1.5mm, var -1.5mm], end = [var 1.5mm, var 1.5mm])
/// line3 = line(start = [var 1.5mm, var 1.5mm], end = [var -1.5mm, var 1.5mm])
/// line4 = line(start = [var -1.5mm, var 1.5mm], end = [var -1.5mm, var -1.5mm])
/// coincident([line1.end, line2.start])
/// coincident([line2.end, line3.start])
/// coincident([line3.end, line4.start])
/// coincident([line4.end, line1.start])
/// parallel([line2, line4])
/// parallel([line3, line1])
/// perpendicular([line1, line2])
/// horizontal(line3)
/// equalLength([line2, line3])
/// distance([line2.start, line2.end]) == 3mm
/// }
///
/// cube1 = extrude(region(point = [0mm, 0mm], sketch = cubeProfile), length = 3mm)
///
/// // Define a custom plane, into which a hole will be cut.
/// customPlane = {
/// origin = { x = 1.5, y = -1.5, z = 3 },
/// xAxis = { x = -0.5, y = -0.5, z = 0 },
/// yAxis = { x = 0, y = 0.5, z = 0.5 }
/// }
///
/// // Cut the hole through the cube, into the plane.
/// hole::holeAt(
/// [cube1],
/// plane = customPlane,
/// holeBottom = hole::flat(),
/// holeBody = hole::blind(depth = 2, diameter = 1),
/// holeType = hole::counterbore(diameter = 1.4, depth = 1),
/// )
/// ```
@(feature_tree = false)
export fn holeAt(
/// Which solid to add a hole to.
@solids: [Solid; 1+],
/// The plane's origin determines the hole's center.
/// The plane's X and Y axis determine which way the hole is pointing
/// (it points down the normal i.e. Z axis, following the right-hand rule).
plane: Plane,
/// Define bottom feature of the hole. E.g. drilled or flat.
holeBottom,
/// Define the main length of the hole. E.g. a blind distance.
holeBody,
/// Define the top feature of the hole. E.g. countersink, counterbore, simple.
holeType,
) {
return subtract(
solids,
tools = holeAtGeometry(
plane,
holeBottom,
holeBody,
holeType,
),
)
}