Tags, Fillets and Chamfers
- Motivation: Applying a fillet
- Tagging edges
- Relationships between edges
- Chamfers
- Measuring with tags
Motivation: Applying a fillet
When you manufacture a part, you often want to smooth off its sharp edges, so they're rounded and won't accidentally cut someone who holds it. Let's say we're modeling a cube, like this:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0])
|> line(end = [0, length])
|> line(end = [-length, 0])
|> line(end = [0, -length])
|> close()
|> extrude(length = length)
It produces a cube like this:
line
calls. How can we refer back to them? Usually, to use some data in an operation, we just put that data into a variable, and pass it into a function. That won't work here, because the data is in a pipeline. So what do we do?
Tagging edges
Simple: we tag the line. A tag is a reference to some data. Let's declare our first tag. We'll modify the above program by adding a tag to one of the lines, like this:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $side) // <- Add the `tag` argument here!
|> line(end = [0, length])
|> line(end = [-length, 0])
|> line(end = [0, -length])
|> close()
|> extrude(length = length)
You declare a tag with a dollar sign, followed by its name, like $side
. This is a new data type, called a TagDeclarator. TagDeclarators can be passed around just like any other kind of data (number, string, etc). Tagging a line is very similar to declaring a variable. Both tags and variables store data, which can be referenced later. Many KCL functions have an optional tag
argument, including all the path-creating functions we've seen, like line
, tangentialArc
, xLine
, etc.
Let's use this tag to make a fillet. Add the line |> fillet(radius = 5, tags = [side])
to the end of the previous program:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $side)
|> line(end = [0, length])
|> line(end = [-length, 0])
|> line(end = [0, -length])
|> close()
|> extrude(length = length)
|> fillet(radius = 5, tags = [side])
The fillet
function accepts an argument tags
, which expects an array of one or more tags. Note that we passed in side
, not $side
. The latter would be declaring a new tag, but we actually want to reference an existing tag. So we didn't use the $
.
That program should produce a cube with one filleted edge, like this:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [0, length], tag = $b)
|> line(end = [-length, 0], tag = $c)
|> line(end = [0, -length], tag = $d)
|> close()
|> extrude(length = length)
|> fillet(radius = 5, tags = [a, c, b, d])
Relationships between edges
We've seen how to tag edges, and reference those tags later to alter edges. What about edges we don't create directly? For example, we've already filleted the four bottom edges, but how do we fillet the top four edges? We aren't creating them via line
calls. They're created by the CAD engine in the extrude
call. If we didn't explicitly create them with a sketch function, how do we tag them? Here's the secret --- you don't. KCL has a few helpful functions to access edges that you didn't create directly. Because we tagged the bottom edges, we can use helper functions like getOppositeEdge
to reference the top edges, like this:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $side)
|> line(end = [0, length])
|> line(end = [-length, 0])
|> line(end = [0, -length])
|> close()
|> extrude(length = length)
|> fillet(radius = 5, tags = [side, getOppositeEdge(side)])
getOppositeEdge
on each:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [0, length], tag = $b)
|> line(end = [-length, 0], tag = $c)
|> line(end = [0, -length], tag = $d)
|> close()
|> extrude(length = length)
|> fillet(radius = 5, tags = [a, c, b, d, getOppositeEdge(a), getOppositeEdge(c), getOppositeEdge(b), getOppositeEdge(d)])
getNextAdjacentEdge
and getPreviousAdjacentEdge
to reference them:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [0, length], tag = $b)
|> line(end = [-length, 0], tag = $c)
|> line(end = [0, -length], tag = $d)
|> close()
|> extrude(length = length)
|> fillet(
radius = 2,
tags = [
a,
getNextAdjacentEdge(a),
getPreviousAdjacentEdge(a)
],
)
a
just like we did before. But we've also filleted the sides adjacent to it. We can use a similar trick to fillet all four vertical side edges:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [0, length], tag = $b)
|> line(end = [-length, 0], tag = $c)
|> line(end = [0, -length], tag = $d)
|> close()
|> extrude(length = length)
|> fillet(
radius = 2,
tags = [
getNextAdjacentEdge(a),
getPreviousAdjacentEdge(a),
getNextAdjacentEdge(c),
getPreviousAdjacentEdge(c),
],
)
Chamfers
A chamfer
is just like a fillet, except that fillets smooth away an edge to make it round, but chamfers just make a single cut across an edge. Here's an example of the difference. Compare this chamfered cube with the filleted cubes above:
length = 20
cube = startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [0, length], tag = $b)
|> line(end = [-length, 0], tag = $c)
|> line(end = [0, -length], tag = $d)
|> close()
|> extrude(length = length)
|> chamfer(
length = 2,
tags = [
getOppositeEdge(a),
],
)
getOppositeEdge
to reference other geometry elsewhere in the model. But tags aren't just used for altering edges. They provide a valuable way to query and measure your models. Let's see how.
Measuring with tags
Let's say you've got a triangle, like this:
length = 20
startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0])
|> line(end = [length, length * 2])
|> line(endAbsolute = profileStart())
Let's ask a simple question. How long is each side of the triangle?
It sounds simple, but to actually calculate it, you'd have to break out a pencil and paper, then do some trigonometry. The problem is, the length doesn't appear anywhere in the line
call. The lines are defined by their start and end points, and the length is an implicit property of those. Defining lines as a start and end is helpful, but it means important properties, like length, can't be read from our source code.
However, tags give us a simple way to refer to each line, and then query them for properties like length with the segLen
function. Let's update our program:
length = 20
startSketchOn(XY)
|> startProfile(at = [-length, -length])
|> line(end = [length, 0], tag = $a)
|> line(end = [length, length * 2], tag = $b)
|> line(endAbsolute = profileStart(), tag = $c)
lenA = segLen(a)
lenB = segLen(b)
lenC = segLen(c)
Now you can open up the Variables pane and look at the lenA
, lenB
and lenC
variables to find each side's length. That's pretty useful! And if you want to use those lengths elsewhere in your code, you can! You could start drawing lines where the end is [lenA, 0]
for example, or plug those lengths into other calculations.
KCL has several other helper functions, like segAng
, which helps you find the angle between two lines. Let's measure the angles in a right-angle triangle:
startSketchOn(XY)
|> startProfile(at = [0, 0])
|> xLine(length = 20)
|> yLine(length = 10, tag = $b)
|> line(endAbsolute = profileStart(), tag = $c)
angleB = segAng(b)
angleC = segAng(c)
You can open up the Variables panel and view the relevant angles! There are other helpers too, like segStart
and segEnd
to find a line's start and end, respectively. Take a look at the KCL standard library docs to find them all.
KCL's tagging system is simple, but powerful. It lets you build up a model (like a cube) from a simple flat shape (your square) and a transformation (like extrusion). Although the transformations create a lot of geometry (for instance, this single extrude call creates 8 edges and five faces), you don't need verbose, complicated labels for all of these features. Instead, you can tag the geometry you've explicitly created, and use simple functions like getOppositeEdge
to reference related geometry. This is much easier than trying to label every edge and face in a model. In the next chapter, we'll explore more interesting uses of tags, like starting new sketches from existing 3D models.