Skip to content

Faces & Edges

Every body in llmcad has named faces and accessible edges. This is the core feature that makes llmcad LLM-friendly --- geometry is referenced by name, not by index or coordinate.

FaceRef

A FaceRef is a named reference to a face on a body. It carries:

  • A name ("top", "front", "wall", ...)
  • A local coordinate system (x_dir, y_dir, normal)
  • Access to edges, corners, and geometric properties

Accessing faces

Standard faces are available as properties:

box = Box(100, 60, 10)

box.top       # +Z face
box.bottom    # -Z face
box.front     # -Y face
box.back      # +Y face
box.right     # +X face
box.left      # -X face

Custom faces are accessed through the faces collection:

box.faces["top"]        # same as box.top
box.faces["_end"]       # end cap of an extrusion
box.faces["wall"]       # cylindrical face

Face properties

face = box.top

face.name       # "top"
face.normal     # Vec3(0, 0, 1) — outward normal
face.center     # Position at face center
face.width      # extent along local x_dir
face.height     # extent along local y_dir
face.area       # surface area in mm^2

Face edges

Every face provides edge access:

face.edges         # all edges of this face
face.left_edge     # leftmost edge (in face-local coords)
face.right_edge    # rightmost edge
face.top_edge      # topmost edge
face.bottom_edge   # bottommost edge
face.long_edges    # the longest edges

These directional edges use the face's local coordinate system, so left_edge is always "left on this face" regardless of the face's orientation in 3D space.

Face corners

Rectangular faces have named corners:

face.corners.TL   # top-left Position
face.corners.TR   # top-right Position
face.corners.BL   # bottom-left Position
face.corners.BR   # bottom-right Position

Corners are Position objects, so they support offsets and insets:

# Place a hole near the top-right corner
pos = box.top.corners.TR.inset(dx=10, dy=10)
hole = extrude(Circle(5).place_on(box.top, at=pos), through=True)

You can iterate over all corners:

for corner in box.top.corners:
    pos = corner.inset(dx=8, dy=8)
    hole = extrude(Circle(4).place_on(box.top, at=pos), through=True)
    box = box - hole

EdgeRef

An EdgeRef wraps a single edge with convenient accessors.

Edge properties

edge = box.top.left_edge

edge.midpoint   # Position at the edge center
edge.length     # edge length in mm
edge.start      # Position at start of edge
edge.end        # Position at end of edge

Combining edges

Edges can be combined with + for passing to fillet() or chamfer():

edges = box.top.left_edge + box.top.right_edge
box = fillet(edges, radius=3)

Automatic face naming

Box faces

Boxes automatically get top, bottom, front, back, left, right based on face normals.

Cylinder faces

Cylinders get top, bottom, wall.

Sphere faces

Spheres get surface.

Extrusion faces

After extrude():

  • _start --- face at the extrusion origin
  • _end --- face at the extrusion tip
  • wall --- cylindrical wall (for circular sketches)
  • Standard faces (top, bottom, etc.) are assigned if they match cardinal normals

After booleans

Face names are automatically transferred through boolean operations using geometric matching. The algorithm matches faces by center position and normal direction.

FaceCollection

body.faces returns a FaceCollection with dict-like access and powerful filtering:

body.faces["top"]          # by name
"top" in body.faces        # membership test
body.faces.start           # shortcut for body.faces["_start"]
body.faces.end             # shortcut for body.faces["_end"]

EdgeCollection

body.edges returns an EdgeCollection:

all_edges = body.edges.all()   # all edges as ShapeList
for edge in body.edges:        # iterate all edges
    print(edge.length)

Selectors

llmcad provides X, Y, Z axis objects that create filter predicates via Python's comparison operators. Import them alongside shapes:

from llmcad import Box, Cylinder, X, Y, Z, fillet

Filtering by position

Use Z.max, Z.min, Z == value, Z > value, Z.between(lo, hi):

box = Box(width=20, length=20, height=10)
hole = Cylinder(diameter=8, height=12)
part = box - hole

# All faces/edges at the highest or lowest position along an axis
part.faces.filter(Z.max)            # topmost face(s)
part.edges.filter(Z.min)            # bottom edge group
part.faces.filter(X.max)            # rightmost face(s)

# Exact position
part.edges.filter(Z == 5.0)         # edges at Z=5

# Comparisons
part.edges.filter(Z > 0)            # edges above Z=0
part.edges.filter(Z <= 3.0)         # edges at or below Z=3

# Range
part.edges.filter(Z.between(2, 8))  # edges with Z center in [2, 8]

Filtering by geometry type

Use type= to filter by geometric shape:

# Faces
part.faces.filter(type="plane")      # all planar faces
part.faces.filter(type="cylinder")   # all cylindrical faces
part.faces.filter(type="sphere")     # spherical faces
part.faces.filter(type="cone")       # conical faces

# Edges
part.edges.filter(type="circle")     # all circular/arc edges
part.edges.filter(type="line")       # all straight edges
part.edges.filter(type="bspline")    # B-spline edges

Filtering by direction

Use axis= for edges parallel to an axis, normal= for face normal direction:

# Edges parallel to an axis
part.edges.filter(axis="Z")          # vertical edges
part.edges.filter(axis="X")          # edges along X

# Faces by normal direction
part.faces.filter(normal="+Z")       # faces pointing up
part.faces.filter(normal="-Z")       # faces pointing down
part.faces.filter(normal="Z")        # faces pointing up or down
part.faces.filter(normal="+X")       # faces pointing right

Combining filters

All filter arguments can be combined in a single call. Multiple axis predicates can be joined with &:

# Top straight edges (for filleting)
part.edges.filter(Z.max, type="line")

# Topmost planar face
part.faces.filter(Z.max, type="plane")

# Compound axis filters
part.edges.filter((Z > 0) & (X > 0))    # top-right quadrant edges

# Lambda predicates
part.edges.filter(lambda e: e.length > 15)

Sorting

Use .sort() to order by position or property, then index with [-1], [0], or slicing:

# Single topmost face
part.faces.sort(axis="z")[-1]

# Single bottommost face
part.faces.sort(axis="z")[0]

# Largest circular edge (by radius)
part.edges.filter(type="circle").sort(key="radius")[-1]

# Shortest edge
part.edges.sort(key="length")[0]

# Largest face by area
part.faces.sort(key="area")[-1]

Chaining

filter() and sort() return a ShapeList that supports further filtering and sorting:

# Topmost planar face
part.faces.filter(type="plane").sort(axis="z")[-1]

# Top circle edges sorted by radius
part.edges.filter(Z.max, type="circle").sort(key="radius")

# Filter, then filter again
part.faces.filter(type="plane").filter(Z.max)

Common patterns

from llmcad import Box, Cylinder, X, Y, Z, fillet, chamfer

box = Box(width=50, length=50, height=20)
hole = Cylinder(diameter=10, height=30)
part = box - hole

# Fillet just the top edges
part = fillet(part.edges.filter(Z.max), radius=2)

# Fillet only vertical edges
part = fillet(part.edges.filter(axis="Z"), radius=3)

# Fillet top straight edges (not the hole circle)
part = fillet(part.edges.filter(Z.max, type="line"), radius=2)

# Chamfer bottom edges
part = chamfer(part.edges.filter(Z.min), size=1)