RasmahRasmah

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_2dFunction
circle_2d(r) -> Circle2D

Construct 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)
source
vtk(extrude(circle_2d(2.0), 1.0))
Rasmah.rect_2dFunction
rect_2d(w, h) -> Rect2D

Construct 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)
source
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_2dFunction
polygon_2d(points) -> Polygon2D

Construct 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)
source
vtk(extrude(polygon_2d([[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]), 1.0))
Rasmah.spline_2dFunction
spline_2d(points; n = 64) -> Spline2D

Construct 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)
source
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_2dFunction
ellipse_2d(rx, ry) -> Ellipse2D

Construct 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)
source
vtk(extrude(ellipse_2d(2.0, 1.0), 1.0))
Rasmah.slot_2dFunction
slot_2d(width, height, radius) -> Slot2D

Construct 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)
source
vtk(extrude(slot_2d(4.0, 2.0, 0.5), 1.0))
Rasmah.regular_polygon_2dFunction
regular_polygon_2d(n::Int, r) -> Polygon2D

Construct 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)
source
vtk(extrude(regular_polygon_2d(6, 1.0), 1.0))
Rasmah.arc_2dFunction
arc_2d(radius, θ1, θ2) -> Arc2D

Construct 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)
source
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^2

For 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.extrudeFunction
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 (see circle_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)
source
volume(extrude(circle_2d(2.0), 3.0))
37.69911184307752 m^3

A 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.revolveFunction
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 a datum_axis for 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)
source
vtk(revolve(rect_2d(2.0, 3.0)))
volume(revolve(rect_2d(2.0, 3.0)))
9.42477796076938 m^3

Here $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.SketchPointType
SketchPoint

A 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 θ.
source
Rasmah.SketchLineType
SketchLine

A sketch line between two SketchPoints.

Fields

  • p::SketchPoint: start point.
  • q::SketchPoint: end point.
source
Rasmah.SketchCircleType
SketchCircle

A sketch circle with a centre point and a radius slot.

Fields

  • c::SketchPoint: centre.
  • r::Int: index of the radius in θ.
source
Rasmah.sketch_pointFunction
sketch_point(i) -> SketchPoint

Construct a SketchPoint referencing θ[i] and θ[i+1] as (x, y).

source

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.verticalFunction
vertical(l) -> VerticalConstraint

Constrain a SketchLine to be vertical.

source
Rasmah.fixedFunction
fixed(p, x, y)

Fix a SketchPoint p at the absolute coordinates (x, y).

source

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_sketchFunction
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.

source

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.0

The 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^2
volume(extrude(poly, 0.5))
1.0000000000000047 m^3

A $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.