Surface Modeling
Surface modeling is employed to create objects with non-planar surfaces that can’t be
generated using functions like extrude()
,
sweep()
, or revolve()
. Since there are no
specific builders designed to assist with the creation of non-planar surfaces or objects,
the following should be considered a more advanced technique.
As described in the topology_ section, a BREP model consists of vertices, edges, faces, and other elements that define the boundary of an object. When creating objects with non-planar faces, it is often more convenient to explicitly create the boundary faces of the object. To illustrate this process, we will create the following game token:
There are several methods of the Face
class that can be used to create
non-planar surfaces:
In this case, we’ll use the make_surface
method, providing it with the edges that define
the perimeter of the surface and a central point on that surface.
To create the perimeter, we’ll use a BuildLine
instance as follows. Since the heart is
symmetric, we’ll only create half of its surface here:
with BuildLine() as heart_half:
l1 = JernArc((0, 0), (1, 1.4), 40, -17)
l2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)
l3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))
l4 = ThreePointArc(l3 @ 1, Vector(0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)
Note that l4
is not in the same plane as the other lines; it defines the center line
of the heart and archs up off Plane.XY
.
In preparation for creating the surface, we’ll define a point on the surface:
surface_pnt = l2.edge().arc_center + Vector(0, 0, 1.5)
We will then use this point to create a non-planar Face
:
top_right_surface = -Face.make_surface(heart_half.wire(), [surface_pnt]).locate(
Pos(Z=0.5)
)
Note that the surface was raised up by 0.5 using the locate method. Also, note that
the -
in front of Face
simply flips the face normal so that the colored side
is up, which isn’t necessary but helps with viewing.
Now that one half of the top of the heart has been created, the remainder of the top and bottom can be created by mirroring:
top_left_surface = top_right_surface.mirror(Plane.YZ)
bottom_right_surface = top_right_surface.mirror(Plane.XY)
bottom_left_surface = -top_left_surface.mirror(Plane.XY)
The sides of the heart are going to be created by extruding the outside of the perimeter as follows:
left_wire = Wire([l3.edge(), l2.edge(), l1.edge()])
left_side = Face.extrude(left_wire, (0, 0, 1)).locate(Pos(Z=-0.5))
right_side = left_side.mirror(Plane.YZ)
With the top, bottom, and sides, the complete boundary of the object is defined. We can
now put them together, first into a Shell
and then into a
Solid
:
heart = Solid(
Shell(
[
top_right_surface,
top_left_surface,
bottom_right_surface,
bottom_left_surface,
left_side,
right_side,
]
)
)
Note
When creating a Solid from a Shell, the Shell must be “water-tight,” meaning it should have no holes. For objects with complex Edges, it’s best practice to reuse Edges in adjoining Faces whenever possible to avoid slight mismatches that can create openings.
Finally, we’ll create the frame around the heart as a simple extrusion of a planar shape defined by the perimeter of the heart and merge all of the components together:
with BuildPart() as heart_token: with BuildSketch() as outline: with BuildLine(): add(l1) add(l2) add(l3) Line(l3 @ 1, l1 @ 0) make_face() mirror(about=Plane.YZ) center = outline.sketch offset(amount=2, kind=Kind.INTERSECTION) add(center, mode=Mode.SUBTRACT) extrude(amount=2, both=True) add(heart)
Note that an additional planar line is used to close l1
and l3
so a Face
can be created. The offset()
function defines the outside of
the frame as a constant distance from the heart itself.
Summary
In this tutorial, we’ve explored surface modeling techniques to create a non-planar
heart-shaped object using build123d. By utilizing methods from the Face
class, such as make_surface()
, we constructed the perimeter and
central point of the surface. We then assembled the complete boundary of the object
by creating the top, bottom, and sides, and combined them into a Shell
and eventually a Solid
. Finally, we added a frame around the heart
using the offset()
function to maintain a constant distance
from the heart.