Sketches, extrusion, and revolution
Overview
So far every shape was built directly in 3D. But many real parts start as a 2D sketch — a flat outline — which is then swept into a solid by moving it through space. This chapter introduces the sketch primitives and the two operations that turn them into solids: extrusion (push the outline straight) and revolution (spin the outline around an axis).
The idea is familiar: a pipe is a circle dragged along a line, and a vase is a profile spun around a vertical axis. Because a sketch is itself a signed distance field, an extruded or revolved solid is just a new signed distance field built from it — so everything stays differentiable.
What is a sketch?
A sketch is a flat signed distance field in the xy plane: a function $s(x, y)$ that is negative inside the outline, zero on it, and positive outside, with $|s|$ the distance to the outline. Rasmah's sketches all have this in common, which is why any sketch can be fed to extrude, revolve, sweep, or loft interchangeably.
The basic sketches
Circle and rectangle
Rasmah.circle_2d — Function
circle_2d(r) -> Circle2DConstruct a Circle2D sketch of radius r.
A sketch is a flat 2D signed-distance field (negative inside), used as the profile for extrude, revolve, sweep, and loft. A circle's field is $\sqrt{x^2 + y^2} - r$, and its area is the exact $\pi r^{2}$.
Arguments
r: the circle radius.
Example
julia> volume(extrude(circle_2d(2.0), 3.0))
37.69911184307752 m^3
julia> vtk(extrude(circle_2d(2.0), 1.0))
VTKView(640×480, inline)vtk(extrude(circle_2d(2.0), 1.0))Rasmah.rect_2d — Function
rect_2d(w, h) -> Rect2DConstruct a Rect2D sketch of width w and height h.
The rectangle is centred at the origin with full extents $w \times h$. Its signed distance field is the exact 2D box SDF, and its area is the exact $w\cdot h$.
Arguments
w: the full width.h: the full height.
Example
julia> area(rect_2d(4.0, 2.0))
8.0 m^2
julia> vtk(extrude(rect_2d(4.0, 2.0), 1.0))
VTKView(640×480, inline)vtk(extrude(rect_2d(4.0, 2.0), 1.0))Polygon and spline
A polygon is a straight-edged outline; a spline is a smooth curve through the same control points.
Rasmah.polygon_2d — Function
polygon_2d(points) -> Polygon2DConstruct a Polygon2D sketch from a list of points (≥ 3).
The polygon is a straight-edged closed sketch: its signed distance field is sign(point-in-polygon) × distance to the nearest edge, where the point-in-polygon test uses even–odd ray casting and the sign is piecewise-constant. Its area is the shoelace formula, so it is exact for any simple polygon.
Arguments
points: the polygon vertices, in order (≥ 3).
Example
julia> area(polygon_2d([[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]))
4.0 m^2
julia> vtk(extrude(polygon_2d([[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]), 1.0))
VTKView(640×480, inline)vtk(extrude(polygon_2d([[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]), 1.0))Rasmah.spline_2d — Function
spline_2d(points; n = 64) -> Spline2DConstruct a Spline2D sketch from control points (≥ 3).
The spline is a smooth closed Catmull–Rom curve that interpolates its control points. It is sampled into a polygon of n edges once at construction, and its signed distance field (and area) are computed from that polygon, so both are differentiable in the control points.
Arguments
points: the control points (≥ 3).
Keyword arguments
n = 64: the number of polygon edges used to sample the curve.
Example
julia> area(spline_2d([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0], [0.0, -1.0]])) > 0
true
julia> vtk(extrude(spline_2d([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0], [0.0, -1.0]]), 1.0))
VTKView(640×480, inline)vtk(extrude(spline_2d([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0], [0.0, -1.0]]), 1.0))Ellipse, slot, regular polygon, arc
Rasmah.ellipse_2d — Function
ellipse_2d(rx, ry) -> Ellipse2DConstruct an Ellipse2D sketch of half-axes rx, ry.
The ellipse uses the algebraic distance field $\sqrt{(x/r_x)^2 + (y/r_y)^2} - 1$ (negative inside) rather than the exact Euclidean distance, which has no closed form. Its area is the exact $\pi\, r_x r_y$.
Arguments
rx: the x half-axis.ry: the y half-axis.
Example
julia> area(ellipse_2d(2.0, 1.0))
6.283185307179586 m^2
julia> vtk(extrude(ellipse_2d(2.0, 1.0), 1.0))
VTKView(640×480, inline)vtk(extrude(ellipse_2d(2.0, 1.0), 1.0))Rasmah.slot_2d — Function
slot_2d(width, height, radius) -> Slot2DConstruct a Slot2D sketch of full width/height and corner radius.
A slot is a rounded rectangle (a "stadium" when radius = height/2). Its area is the exact $w\cdot h - (4 - \pi) r^{2}$.
Arguments
width: the full width.height: the full height.radius: the corner radius.
Example
julia> area(slot_2d(4.0, 2.0, 0.5))
7.785398163397448 m^2
julia> vtk(extrude(slot_2d(4.0, 2.0, 0.5), 1.0))
VTKView(640×480, inline)vtk(extrude(slot_2d(4.0, 2.0, 0.5), 1.0))Rasmah.regular_polygon_2d — Function
regular_polygon_2d(n::Int, r) -> Polygon2DConstruct a regular n-gon sketch with circumradius r (first vertex on the +x axis), built on the polygon SDF.
Arguments
n: the number of sides (n ≥ 3).r: the circumradius.
Example
julia> area(regular_polygon_2d(6, 1.0)) == 6 * sqrt(3) / 4 * 1.0^2
true
julia> vtk(extrude(regular_polygon_2d(6, 1.0), 1.0))
VTKView(640×480, inline)vtk(extrude(regular_polygon_2d(6, 1.0), 1.0))Rasmah.arc_2d — Function
arc_2d(radius, θ1, θ2) -> Arc2DConstruct an Arc2D sector sketch of radius spanning θ1→θ2 (radians).
The arc is a pie-slice region: the exact signed distance to the sector boundary (the circular arc plus its two radial edges). Its area is $(\theta_2 - \theta_1)/2\cdot r^{2}$.
Arguments
radius: the arc radius.θ1: the start angle (radians).θ2: the end angle (radians).
Example
julia> area(arc_2d(2.0, 0.0, pi)) == pi/2 * 4.0
true
julia> vtk(extrude(arc_2d(2.0, 0.0, pi), 1.0))
VTKView(640×480, inline)vtk(extrude(arc_2d(2.0, 0.0, pi), 1.0))Every sketch can report its exact area:
area(circle_2d(2.0))12.566370614359172 m^2For a circle, $A = \pi r^{2} = \pi \cdot 4 \approx 12.566$.
Extrusion
Extrusion pushes a sketch straight along the z axis, giving it a thickness. The solid is the set of points that are inside the sketch and within the axial span, so its volume is simply $V = \text{area} \cdot \text{height}$:
Rasmah.extrude — Function
extrude(sketch, distance)Extrude a 2D sketch by distance along the z-axis, turning it into a solid.
Extrusion sweeps a flat profile perpendicular to its plane. The resulting SDF is max(sketch(x, y), |z| − distance/2) — a point is inside when it is inside the sketch and within the axial span. The volume is area(sketch) · distance.
Arguments
sketch: a 2D sketch (seecircle_2d,rect_2d,polygon_2d, …).distance: the extrusion length.
Returns
An Extrude feature node.
Example
julia> using Rasmah
julia> volume(extrude(circle_2d(2.0), 3.0))
37.69911184307752 m^3
julia> vtk(extrude(circle_2d(2.0), 3.0))
VTKView(640×480, inline)volume(extrude(circle_2d(2.0), 3.0))37.69911184307752 m^3A circle of area $4\pi$ pushed $3$ units has volume $12\pi \approx 37.70$.
Revolution
Revolution spins a sketch around an axis. A full turn of a rectangle makes a cylinder; a full turn of a half-disk makes a sphere.
Rasmah.revolve — Function
revolve(profile, angle=2π; axis=3)Revolve a 2D profile about a coordinate axis by angle (radians).
Revolution spins the flat profile around an axis to make a solid of revolution. For a full turn (angle = 2π) the profile must be positioned so it does not cross the axis. The volume follows Pappus's centroid theorem; for a circle it is a sphere, for a rectangle a cylinder.
Arguments
profile: the 2D profile to revolve.angle: the sweep angle in radians (default $2\pi$, a full revolution).
Keyword arguments
axis = 3: the revolution axis (1 = x,2 = y,3 = z), or adatum_axisfor an arbitrary axis.
Returns
A Revolve feature node.
Example
julia> using Rasmah
julia> volume(revolve(rect_2d(2.0, 3.0)))
9.42477796076938 m^3
julia> vtk(revolve(rect_2d(2.0, 3.0)))
VTKView(640×480, inline)vtk(revolve(rect_2d(2.0, 3.0)))volume(revolve(rect_2d(2.0, 3.0)))9.42477796076938 m^3Here $rect_2d(2, 3)$ is a $2 \times 3$ rectangle centred on the origin, so it straddles the z axis. Revolution keeps only the half at $x \ge 0$ (a profile may not cross the axis), sweeping a disk of radius $1$ through a height of $3$: $V = \pi r^{2} h = 3\pi \approx 9.42$.
The lesson: a profile's position relative to the axis matters. To sweep a cylinder of radius $2$ you would first move the rectangle off the axis (e.g. revolve(translate(rect_2d(2.0, 3.0), 2, 0, 0))), so that the region $x \in [1, 3]$ rotates into a hollow tube.
Parametric sketches with constraints
The free-form sketches above are built by typing coordinates. A parametric sketch is built the other way: you place points and lines roughly, then declare how they relate — this line is horizontal, this gap is $2$, this corner is fixed — and let the solver place them exactly. That is what makes a sketch parametric: change one number and the whole sketch re-solves.
A sketch is a flat parameter vector $\theta$ — an $(x, y)$ slot per point, plus scalar slots for radii and angles — and a list of constraints. The entities are thin references into those slots:
Rasmah.SketchPoint — Type
SketchPointA sketch point referencing two entries (x, y) of the sketch parameter vector θ at indices i and i + 1.
Fields
i::Int: index of the x-coordinate inθ.
Rasmah.SketchLine — Type
SketchLineA sketch line between two SketchPoints.
Fields
p::SketchPoint: start point.q::SketchPoint: end point.
Rasmah.SketchCircle — Type
SketchCircleA sketch circle with a centre point and a radius slot.
Fields
c::SketchPoint: centre.r::Int: index of the radius inθ.
Rasmah.sketch_point — Function
sketch_point(i) -> SketchPointConstruct a SketchPoint referencing θ[i] and θ[i+1] as (x, y).
Geometric constraints express shape. The core set makes a line horizontal or vertical, pins a point, or relates two entities (coincident, parallel, perpendicular, tangent, concentric, equal, midpoint, symmetric):
Rasmah.horizontal — Function
horizontal(l) -> HorizontalConstraintConstrain a SketchLine to be horizontal.
Rasmah.vertical — Function
vertical(l) -> VerticalConstraintConstrain a SketchLine to be vertical.
Rasmah.fixed — Function
fixed(p, x, y)Fix a SketchPoint p at the absolute coordinates (x, y).
Dimensional constraints express size: distance, length, radius, diameter, and angle_constraint all fix a numeric value. The constraints are collected into a Sketch and solved:
Rasmah.solve_sketch — Function
solve_sketch(s; tol=1e-10, max_iter=200, method=:lm, damp=1e-6) -> θSolve a Sketch's constraints (Gauss–Newton or Levenberg–Marquardt) and return the solved parameter vector. Warns (but still returns the last iterate) if the solver does not converge.
Rasmah.polygon_from_points — Function
polygon_from_points(points, θ) -> Polygon2DBuild a Polygon2D from solved SketchPoints at the parameter vector θ.
A worked example: a rectangle that solves itself
Start with four points arranged in a slightly-skewed rectangle, and declare what they should be — horizontal and vertical sides, a $2 \times 1$ size, and one corner fixed at the origin:
p1 = sketch_point(1); p2 = sketch_point(3); p3 = sketch_point(5); p4 = sketch_point(7)
l1 = SketchLine(p1, p2); l2 = SketchLine(p2, p3); l3 = SketchLine(p3, p4); l4 = SketchLine(p4, p1)
θ0 = [0.0, 0.0, 2.0, 0.1, 1.9, 1.0, 0.0, 0.9]
s = Sketch(θ0, [
horizontal(l1), vertical(l2), horizontal(l3), vertical(l4),
distance(p1, p2, 2.0), distance(p2, p3, 1.0),
fixed(p1, 0.0, 0.0),
])
θ = solve_sketch(s)
round.(θ; digits = 4)8-element Vector{Float64}:
0.0
0.0
2.0
0.0
2.0
1.0
0.0
1.0The initial guess was a box with a slight skew (the top corners sat at $y = 0.1$ and $x = 1.9$). The solver corrected it to a perfect rectangle — $(0,0), (2,0), (2,1), (0,1)$ — because the constraints, not the guess, determine the shape. Hand the solved points to a polygon and sweep it:
poly = polygon_from_points([p1, p2, p3, p4], θ)
area(poly)2.0000000000000093 m^2volume(extrude(poly, 0.5))1.0000000000000047 m^3A $2 \times 1$ rectangle extruded $0.5$ units has area $2$ and volume $1$ — exact, because the solved polygon is exact.
Next steps
With sketches and their swept solids in hand, move on to the operations that reshape solids: Modeling operations.
