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():
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 tipwall--- 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:
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)